Callbacks in C# android - c#

I have implemented callbacks on Handler(in java). I need to implement the same on c#(Xamarin). But as of now I am unable to find any solution to how I can do this in C#. I am new to c# hence have very little knowledge.
here is the java code:-
private Handler handler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg)
{
if (msg.what == MSG_SURFACE_CREATED)
{
contentWidth = 0;
contentHeight = 0;
requestLayout();
return true;
}
else
{
Log.w("Unknown msg.what: " + msg.what);
}
return false;
}
});
Any idea can i implement the same in C#?

Events is the way to go (which uses delegates). Here is some sample code:
class CallingClass
{
private SurfaceCreatingClass m_surfacecreatingclass;
public CallingClass()
{
m_surfacecreatingclass = new SurfaceCreatingClass();
m_surfacecreatingclass.SurfaceCreatedHandler += OnFinished;
m_surfacecreatingclass.CreateSurface();
}
void OnFinished(int iMessageWhat)
{
if (iMessageWhat == SurfaceCreatingClass.MSG_SURFACE_CREATED)
{
contentWidth = 0;
contentHeight = 0;
RequestLayout();
}
else
{
Log.w("Unknown msg.what: " + iMessageWhat);
}
}
}
class SurfaceCreatingClass
{
public delegate void SurfaceCreatedDelegate(int iWhat);
public event SurfaceCreatedDelegate SurfaceCreatedHandler;
public const int MSG_SURFACE_CREATED = 1;
public void CreateSurface()
{
/////////////////////////////
// Surface creation code here
// ...
/////////////////////////////
if (SurfaceCreatedHandler != null)
SurfaceCreatedHandler(MSG_SURFACE_CREATED);
}
}

IN Xamarin You still will use this " inline " approach, defined by the keyword "delegate".
This is, because, callbacks are function pointers, c# supports this, java not, and xamarin also not, BUT tries to spawn a bridge.
Delegates in xamarin are described in here:
http://docs.xamarin.com/guides/ios/application_fundamentals/delegates,_protocols,_and_events/

Related

Is there a way to be notified of whenever the volume setting is changed in Xamarin C#? [duplicate]

I'm programming a small widget that needs to be updated whenever the user changes the ringer volume or the vibrate settings.
Capturing android.media.VIBRATE_SETTING_CHANGED works just fine for the vibrate settings, but I haven't found any way of getting notified when the ringer volume changes and although I could try to capture when the user presses the volume up/volume down physical keys, there are many other options for changing the volume without using these keys.
Do you know if there's any broadcast action defined for this or any way to create one or to solve the problem without it?
There is no broadcast action, but I did find you can hook up a content observer to get notified when the settings change, volume of streams being some of those settings. Register for the android.provider.Settings.System.CONTENT_URI to be notified of all settings changes:
mSettingsContentObserver = new SettingsContentObserver( new Handler() );
this.getApplicationContext().getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true,
mSettingsContentObserver );
The content observer might look something like this:
public class SettingsContentObserver extends ContentObserver {
public SettingsContentObserver(Handler handler) {
super(handler);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.v(LOG_TAG, "Settings change detected");
updateStuff();
}
}
And be sure to unregister the content observer at some point.
Nathan's code works but gives two notifications for each change system settings. To avoid that, use the following
public class SettingsContentObserver extends ContentObserver {
int previousVolume;
Context context;
public SettingsContentObserver(Context c, Handler handler) {
super(handler);
context=c;
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
int delta=previousVolume-currentVolume;
if(delta>0)
{
Logger.d("Decreased");
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Increased");
previousVolume=currentVolume;
}
}
}
Then in your service onCreate register it with:
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
Then unregister in onDestroy:
getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
Yes, you can register a receiver for a volume change(this is kind of a hack, but works), I managed to do it this way (does not involve a ContentObserver):
In manifest xml file:
<receiver android:name="com.example.myproject.receivers.MyReceiver" >
<intent-filter>
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
BroadcastReceiver:
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
Log.d("Music Stream", "has changed");
}
}
}
hope it helps!
Based into Nathan's, adi's and swooby's code I created a full working example with some minor improvements.
Looking to the AudioFragment class we can see how easy is to listen for volume changes with our custom ContentObserver.
public class AudioFragment extends Fragment implements OnAudioVolumeChangedListener {
private AudioVolumeObserver mAudioVolumeObserver;
#Override
public void onResume() {
super.onResume();
// initialize audio observer
if (mAudioVolumeObserver == null) {
mAudioVolumeObserver = new AudioVolumeObserver(getActivity());
}
/*
* register audio observer to identify the volume changes
* of audio streams for music playback.
*
* It is also possible to listen for changes in other audio stream types:
* STREAM_RING: phone ring, STREAM_ALARM: alarms, STREAM_SYSTEM: system sounds, etc.
*/
mAudioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
}
#Override
public void onPause() {
super.onPause();
// release audio observer
if (mAudioVolumeObserver != null) {
mAudioVolumeObserver.unregister();
}
}
#Override
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
Log.d("Audio", "Volume: " + currentVolume + "/" + maxVolume);
Log.d("Audio", "Volume: " + (int) ((float) currentVolume / maxVolume) * 100 + "%");
}
}
public class AudioVolumeContentObserver extends ContentObserver {
private final OnAudioVolumeChangedListener mListener;
private final AudioManager mAudioManager;
private final int mAudioStreamType;
private int mLastVolume;
public AudioVolumeContentObserver(
#NonNull Handler handler,
#NonNull AudioManager audioManager,
int audioStreamType,
#NonNull OnAudioVolumeChangedListener listener) {
super(handler);
mAudioManager = audioManager;
mAudioStreamType = audioStreamType;
mListener = listener;
mLastVolume = audioManager.getStreamVolume(mAudioStreamType);
}
/**
* Depending on the handler this method may be executed on the UI thread
*/
#Override
public void onChange(boolean selfChange, Uri uri) {
if (mAudioManager != null && mListener != null) {
int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
if (currentVolume != mLastVolume) {
mLastVolume = currentVolume;
mListener.onAudioVolumeChanged(currentVolume, maxVolume);
}
}
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
}
public class AudioVolumeObserver {
private final Context mContext;
private final AudioManager mAudioManager;
private AudioVolumeContentObserver mAudioVolumeContentObserver;
public AudioVolumeObserver(#NonNull Context context) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public void register(int audioStreamType,
#NonNull OnAudioVolumeChangedListener listener) {
Handler handler = new Handler();
// with this handler AudioVolumeContentObserver#onChange()
// will be executed in the main thread
// To execute in another thread you can use a Looper
// +info: https://stackoverflow.com/a/35261443/904907
mAudioVolumeContentObserver = new AudioVolumeContentObserver(
handler,
mAudioManager,
audioStreamType,
listener);
mContext.getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI,
true,
mAudioVolumeContentObserver);
}
public void unregister() {
if (mAudioVolumeContentObserver != null) {
mContext.getContentResolver().unregisterContentObserver(mAudioVolumeContentObserver);
mAudioVolumeContentObserver = null;
}
}
}
public interface OnAudioVolumeChangedListener {
void onAudioVolumeChanged(int currentVolume, int maxVolume);
}
Hope it's still useful for someone! :)
Nathan's and adi's code works, but can be cleaned up and self-contained to:
public class AudioStreamVolumeObserver
{
public interface OnAudioStreamVolumeChangedListener
{
void onAudioStreamVolumeChanged(int audioStreamType, int volume);
}
private static class AudioStreamVolumeContentObserver
extends ContentObserver
{
private final AudioManager mAudioManager;
private final int mAudioStreamType;
private final OnAudioStreamVolumeChangedListener mListener;
private int mLastVolume;
public AudioStreamVolumeContentObserver(
#NonNull
Handler handler,
#NonNull
AudioManager audioManager, int audioStreamType,
#NonNull
OnAudioStreamVolumeChangedListener listener)
{
super(handler);
mAudioManager = audioManager;
mAudioStreamType = audioStreamType;
mListener = listener;
mLastVolume = mAudioManager.getStreamVolume(mAudioStreamType);
}
#Override
public void onChange(boolean selfChange)
{
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
if (currentVolume != mLastVolume)
{
mLastVolume = currentVolume;
mListener.onAudioStreamVolumeChanged(mAudioStreamType, currentVolume);
}
}
}
private final Context mContext;
private AudioStreamVolumeContentObserver mAudioStreamVolumeContentObserver;
public AudioStreamVolumeObserver(
#NonNull
Context context)
{
mContext = context;
}
public void start(int audioStreamType,
#NonNull
OnAudioStreamVolumeChangedListener listener)
{
stop();
Handler handler = new Handler();
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioStreamVolumeContentObserver = new AudioStreamVolumeContentObserver(handler, audioManager, audioStreamType, listener);
mContext.getContentResolver()
.registerContentObserver(System.CONTENT_URI, true, mAudioStreamVolumeContentObserver);
}
public void stop()
{
if (mAudioStreamVolumeContentObserver == null)
{
return;
}
mContext.getContentResolver()
.unregisterContentObserver(mAudioStreamVolumeContentObserver);
mAudioStreamVolumeContentObserver = null;
}
}
If its only ringer mode change you can use Brodcast receiver with "android.media.RINGER_MODE_CHANGED" as the action. It will easy to implement
Hi i tried the code above and it did not work for me. But when i tried to add this line
getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
and put
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
It works now. My concern is how to hide the volume dialog onchange. See this image.
private const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
private const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"
val filter = IntentFilter(VOLUME_CHANGED_ACTION)
filter.addAction(RINGER_MODE_CHANGED_ACTION)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context1: Context, intent: Intent) {
val stream = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, UNKNOWN)
val mode = intent.getIntExtra(EXTRA_RINGER_MODE, UNKNOWN)
val volumeLevel = audioManager.getStreamVolume(stream)
}
}
100% working way in all cases
public class SettingsContentObserver extends ContentObserver {
SettingsContentObserver(Handler handler) {
super(handler);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
volumeDialogContract.updateMediaVolume(getMediaVolume());
}
int getMediaVolume() {
return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
}
void unRegisterVolumeChangeListener() {
volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().
unregisterContentObserver(settingsContentObserver);
}
void registerVolumeChangeListener() {
settingsContentObserver = new VolumeDialogPresenter.SettingsContentObserver(new Handler());
volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true,
settingsContentObserver);
}

Firing events in C++ and handling them in C#

I have an industrial computer with some Digital I/O pins. The manufacturer provides some C++ libraries and examples to handle pin status change.
I need to integrate this events onto a C# application. AFAIK the most simple way to perform this is:
Make a managed C++/CLI wrapper for the manufacturer libraries that fires events when interruptions are issued from the DIO pins.
Reference that wrapper and handle the events in the C# part as it they were normal C# events.
I have tried to make this work with some mock objects with no luck. From the docs, the function EventHandler should do most of the "dirty work" in my case. Following info available in old threads and the EventHandler example in the MSDN docs I ended up with this test code:
C++/CLI
using namespace System;
public ref class ThresholdReachedEventArgs : public EventArgs
{
public:
property int Threshold;
property DateTime TimeReached;
};
public ref class CppCounter
{
private:
int threshold;
int total;
public:
CppCounter() {};
CppCounter(int passedThreshold)
{
threshold = passedThreshold;
}
void Add(int x)
{
total += x;
if (total >= threshold) {
ThresholdReachedEventArgs^ args = gcnew ThresholdReachedEventArgs();
args->Threshold = threshold;
args->TimeReached = DateTime::Now;
OnThresholdReached(args);
}
}
event EventHandler<ThresholdReachedEventArgs^>^ ThresholdReached;
protected:
virtual void OnThresholdReached(ThresholdReachedEventArgs^ e)
{
ThresholdReached(this, e);
}
};
public ref class SampleHandler
{
public:
static void c_ThresholdReached(Object^ sender, ThresholdReachedEventArgs^ e)
{
Console::WriteLine("The threshold of {0} was reached at {1}.",
e->Threshold, e->TimeReached);
Environment::Exit(0);
}
};
void main()
{
return;
CppCounter^ c = gcnew CppCounter(20);
c->ThresholdReached += gcnew EventHandler<ThresholdReachedEventArgs^>(SampleHandler::c_ThresholdReached);
Console::WriteLine("press 'a' key to increase total");
while (Console::ReadKey(true).KeyChar == 'a') {
Console::WriteLine("adding one");
c->Add(1);
}
}
C#
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
CppCounter cc = new CppCounter(5);
//cc.ThresholdReached += cs_ThresholdReached; //<--This is the offending line
Console.WriteLine("press 'a' key to increase total");
while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
cc.Add(1);
}
}
static void cs_ThresholdReached(object sender, ThresholdReachedEventArgs e)
{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}
class Counter
{
private int threshold;
private int total;
public Counter(int passedThreshold)
{
threshold = passedThreshold;
}
public void Add(int x)
{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}
protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
{
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
}
public class ThresholdReachedEventArgs : EventArgs
{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}
}
What am I doing wrong? Is it something I am missing?
public class ThresholdReachedEventArgs : EventArgs
The code is correct, except for this minor glitch. You accidentally re-declared this class in your C# code. Now there are two, one from your C++/CLI project and another from your C# project. That is a problem, type identity in .NET is not just determined by the namespace name and class name, it also includes the assembly it came from.
So these are two distinct types, the compiler tries to tell you that the C# version of it is not the correct one. That they have the same name doesn't exactly help you decode the error message :)
Very easy to fix, simply delete the class declaration from your C# code. Now the compiler will use the C++/CLI version of it.

How to manage a third-party DLL event within his wrapper DLL

I'm a beginner!
I made a wrapper DLL (DLL_A) of a third-party DLL (DLL_B).
Below you can find a simplyfied example:
The DLL_B class Class_B expose (i can view only the signature from metadata):
public delegate void eveHandler(bool ret_B);
public class cls_B
{
public cls_B(string init);
public event eveHandler eve;
public void req(eveHandler reqHandler = null);
}
Inside DLL_A:
public class cls_A
{
private cls_B objClsB;
private bool continueWorking = true;
public cls_A()
{
objClsB = new cls_B("test");
objClsB.ev += new eveHandler(this.eveManager);
}
public eveManager(bool ret_A)
{
continueWorking = false;
}
public request()
{
objClsB.req();
int i = 0;
While (continueWorking && i < 100)
{
//Do a lot of stuff...
i++;
}
}
}
Then inside the main app:
cls_A objClsA = new cls_A();
objClsA.request();
MessageBox.Show("Done!", "MyApp");
It works, but it seems that the eveManager() is only called when it exit from the objClsA.request(); , before execute MessageBox.Show("Done!", "MyApp");.
In fact if I remove the && i < 100 part it will stuck inside the while loop, but I need that the event stop the loop.
Where I'm wrong?
Thanks in advance!
Solution founded! It works if I start the objClsB.req() on another thread this way:
public request()
{
Thread thr = new Thread(delegate() { objClsB.req(); });
thr.Start();
int i = 0;
While (continueWorking)
{
//Do a lot of stuff...
}
}

Update UI in middle of thread execution c# android

I am currently migrating java code for my android app to C#. I want to update my UI in middle of thread execution.
Here is my java code:-
private Handler handler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_SURFACE_CREATED) {
contentWidth = 0;
contentHeight = 0;
requestLayout();
return true;
} else {
Log.w("Unknown msg.what: " + msg.what);
}
return false;
}
});
And:-
void postChangedToView(final int indexInAdapter) {
handler.post(new Runnable() {
#Override
public void run() {
changedToView(indexInAdapter, true);
}
});
}
I have tried something like this in c# :-
private Android.OS.Handler handler = new Android.OS.Handler();
private class Callback : Android.OS.Handler.ICallback //inner class
{
ViewController fp; //Create instance of outer class
public Callback(FViewController _fp) //pass the instance to constructor of inner class
{
fp = _fp;
}
#region ICallback implementation
public bool HandleMessage (Message msg)
{
if (msg.What == MSG_SURFACE_CREATED)
{
contentWidth = 0;
contentHeight = 0;
fp.RequestLayout ();
return true;
}
else
{
Log.w("Unknown msg.what: " + msg.What);
}
return false;
throw new NotImplementedException ();
}
}
Here I cannot make an inline class of Handler.ICallBack
And:-
internal virtual void postChangedToView(int indexInAdapter) {
handler.Post (Task.Run (()=> flippedToView (indexInAdapter,true)));
}
Here I get an error saying :-
Error CS1503: Argument 1: cannot convert from 'System.Threading.Tasks.Task' to 'System.Action'
Handler.Post requires a System.Action parameter. You can create System.Action as below:
internal virtual void postFlippedToView(int indexInAdapter)
{
Action action = () => flippedToView(indexInAdapter, true);
handler.Post (action );
}

Unity .net Remoting Server get_animation can only be called from the main thread [duplicate]

This question already has answers here:
Use Unity API from another Thread or call a function in the main Thread
(5 answers)
Closed 6 years ago.
I am working on a project where I am having two Unity Projects that need to communicate with each other. I am trying to solve this by using the .net Remoting Framework.
For That I created a dll which both Unity projects will use. The dll consists of:
MyRemotableObject.cs
public class MyRemotableObject : MarshalByRefObject
{
public MyRemotableObject()
{
}
public void NotifyStatusChange(int status)
{
Cache.GetInstance().Status = 0;
}
public int GetCreditCount()
{
return Cache.GetInstance().CreditCount;
}
}
Cache.cs
public class Cache
{
private static Cache myInstance;
public static IObserver Observer;
private Cache()
{
}
public static void Attach(IObserver observer)
{
Observer = observer;
}
public static Cache GetInstance()
{
if(myInstance==null)
{
myInstance = new Cache();
}
return myInstance;
}
public int Status
{
set
{
Observer.NotifyFinished(value);
}
}
public int CreditCount
{
get
{
return Observer.QueryCreditCount();
}
}
}
IObserver.cs
public interface IObserver
{
void NotifyFinished(int status);
int QueryCreditCount();
}
Now I have my Menu - Unity project, acting as the remoting server
MenuController.cs
public class MenuController : MonoBehaviour, IObserver
{
private object lockObject;
List<ControllerBase> controllers;
private MyRemotableObject remotableObject;
private System.ComponentModel.Container components = null;
void Awake()
{
lockObject = new object();
try
{
remotableObject = new MyRemotableObject();
//für fehler: //http://www.mycsharp.de/wbb2/thread.php?postid=199935
//************************************* TCP *************************************//
// using TCP protocol
TcpChannel channel = new TcpChannel(124);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyRemotableObject), "TargetShooterMenu", WellKnownObjectMode.SingleCall);
//************************************* TCP *************************************//
RemotableObjects.Cache.Attach(this);
}
catch (Exception e)
{
Debug.Log(e.ToString());
}
controllers = new List<ControllerBase>();
foreach (GameObject controllerObject in GameObject.FindGameObjectsWithTag(GlobalNames.Tags.CONTROLLEROBJECT))
{
if (controllerObject.GetComponent<ControllerBase>())
controllers.Add(controllerObject.GetComponent<ControllerBase>());
}
}
delegate void PresentNameInputControllerDelegate(int status);
private void PresentNameInputController(int status)
{
if (status == (int)LevelStatusCode.OK)
foreach (ControllerBase controller in controllers)
{
controller.Hide();
if (controller.GetType() == typeof(NameInputController))
controller.Show();
}
}
public void NotifyFinished(int status)
{
Debug.Log("Notify");
lock (lockObject)
{
PresentNameInputControllerDelegate d = PresentNameInputController;
d(status);
}
}
public int QueryCreditCount()
{
Debug.Log("Query");
return 100;
}
}
This Server implements the IObserver Functions NotifyFinished and QueryCreditCount (returns dummy value for the moment)
When calling the NotifyFinished function from the client, following error occurs:
get_animation can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
Can someone tell me, how to solve this problem?
Thanks in advance,
Hoffmanuel
After lots of searching, i came to the solution: Using the Loom Unity Package from:
Unity Gems entry about threading
and using it like mentioned in Unity answers entry about threading:
void Start()
{
var tmp = Loom.Current;
...
}
//Function called from other Thread
public void NotifyFinished(int status)
{
Debug.Log("Notify");
try
{
if (status == (int)LevelStatusCode.OK)
{
Loom.QueueOnMainThread(() =>
{
PresentNameInputController();
});
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
}

Categories