How to detect emergency call only in Xamarin? - xamarin

Is there a way to detect if the phone, at the call try moment, can only make emergency calls (using Xamarin)?

This is something platform specific. Unfortunaelty I don't know any plugin for this, so you have to use the native API in Xamarin.
On Android it is the TelephonyManager as shown here: https://stackoverflow.com/a/14355706/1489968 It's Java, but can be easily translated to C#:
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
var telMng = (TelephonyManager) GetSystemService(TelephonyService);
var myPhoneStateListener = new MyPhoneStateListener();
myPhoneStateListener.ServiceStateChanged += (s, e) => Console.WriteLine("State: {0}", e);
telMng.Listen(myPhoneStateListener, PhoneStateListenerFlags.ServiceState);
}
}
public class MyPhoneStateListener : PhoneStateListener
{
public event EventHandler<ServiceState> ServiceStateChanged;
public override void OnServiceStateChanged(ServiceState serviceState)
{
base.OnServiceStateChanged(serviceState);
ServiceStateChanged?.Invoke(this, serviceState);
}
}

On iOS: Not sure if that information is available, never have seen it exposed via the SDK... (maybe add iOS tag to this question, or ask an iOS only question, answer might be in ObjC/Swift but you can translate it)
On Android: The info you are looking for is contained within the ServiceState of the phone:
var callState = new ServiceState ();
switch (callState.State) {
case PhoneState.InService:
{
var uri = Android.Net.Uri.Parse ("tel:555-2368"); // Jim Rockford's number ;-)
var intent = new Intent (Intent.ActionDial, uri);
StartActivity (intent);
break;
}
case PhoneState.EmergencyOnly:
{
Toast.MakeText (this, "Emergency Calls Only", ToastLength.Long).Show();
break;
}
case PhoneState.OutOfService:
{
Toast.MakeText (this, "Out of Service", ToastLength.Long).Show();
break;
}
case PhoneState.PowerOff:
{
Toast.MakeText (this, "Cell/Modem Power Off", ToastLength.Long).Show();
break;
}
default:
{
Toast.MakeText (this, "Should never be shown on a real device", ToastLength.Long).Show();
break;
}
}
Ref: http://developer.android.com/reference/android/telephony/ServiceState.html
For testing on the emulator, you can set the state to denied via the adb shell:
voice denied
data denied
Ref: https://developer.android.com/tools/devices/emulator.html

Related

Turning Bluetooth Tethering On in Xamarin.Android

I'm currently trying to add some Bluetooth functionality to my app. I want to be able to change the Bluetooth Tethering on or off, as well as check its status.
I found the Java code on StackOverflow: How to check Bluetooth tethering status programmatically in Android
I have translated it into C#, but I don't seem to be able to get any result.
Regardless of the tethering setting, it always shows the toast with "Tethering:false", and the setBluetoothTethering doesn't change anything.
Any idea what I'm missing?
Here's my code:
[...]
try
{
Class classBluetoothPan = Class.ForName("android.bluetooth.BluetoothPan");
Method mBTPanConnect = classBluetoothPan.GetDeclaredMethod("connect", Class.FromType(typeof(BluetoothDevice)));
Constructor BTPanCtor = classBluetoothPan.GetDeclaredConstructor(Class.FromType(typeof(Context)), Class.FromType(typeof(IBluetoothProfileServiceListener)));
BTPanCtor.Accessible = true;
Java.Lang.Object BTSrvInstance = BTPanCtor.NewInstance(Activity, new BTPanServiceListener(Activity));
Method isTetheringOnMethod = classBluetoothPan.GetDeclaredMethod("isTetheringOn", null);
var isTetheringOn = isTetheringOnMethod.Invoke(BTSrvInstance);
Toast.MakeText(Activity, "Tethering:" + isTetheringOn, ToastLength.Short).Show();
Method setBluetoothTetheringMethod = classBluetoothPan.GetDeclaredMethod("setBluetoothTethering", new Class[1] { Class.FromType(typeof(bool)) });
setBluetoothTetheringMethod.Invoke(BTSrvInstance, true);
// tether = !tether;
}
catch (ClassNotFoundException e)
{
e.PrintStackTrace();
}
catch (Java.Lang.Exception e)
{
e.PrintStackTrace();
}
[...]
public class BTPanServiceListener : Java.Lang.Object, IBluetoothProfileServiceListener
{
private Activity _activity;
public BTPanServiceListener(Activity activity)
{
_activity = activity;
}
public void OnServiceConnected([GeneratedEnum] ProfileType profile, IBluetoothProfile proxy)
{
// throw new NotImplementedException();
}
public void OnServiceDisconnected([GeneratedEnum] ProfileType profile)
{
// throw new NotImplementedException();
}
}
I figured out how to enable Bluetooth tethering via setBluetoothTethering.
I wrote an entire blog about this
You can find the final code here
I assume that isTetheringOn works in the same way

How to resume the application and open a specific page with a push notification in Xamarin.forms

I'm currently working on a Xamarin application working both on iOS and Android, but the problem I'm going to explain only concerns the Android application (this is not yet implemented in the iOS app).
Actually, when I receive a given push notification, I need to open a specific page in my application. It works very well if the application is open when the push notification is received, but the app crashes if my app is closed or run in background.
Well, when I receive the notification, I end up in the method called "OnShouldOpenCommand" :
private void OnShouldOpenCommand(string commandId)
{
NotifyNewCommand(AppResources.AppName, AppResources.CommandNotificationText, commandId);
Device.BeginInvokeOnMainThread(() =>
{
try
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
App.MasterDetailPage.NavigationStack.Push(((NavigationPage)(App.MasterDetailPage.Detail)).CurrentPage);
}
catch(Exception e)
{
Log.Debug("PushAsync", "Unable to push CommandAcceptancePage : "+ex.Message);
}
});
}
private void NotifyNewCommand(string Title,string Description, string commandId)
{
var intent = new Intent(this, typeof(MainActivity));
if (!String.IsNullOrEmpty(commandId))
{
intent.PutExtra("CommandId", commandId);
}
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
var notificationBuilder = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Kluox")
.SetContentText(Description)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
Notification notification = notificationBuilder.Build();
var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify(0, notification);
}
And the code
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
is generating an exception of type :
Java.Lang.IllegalStateException: Can not perform this action after
onSaveInstanceState
So well, I suppose I can't access "App" and redirect to another page if my application is not running in foreground. Well, this is when I receive the push notification an not when I click on it. But well, I do not intend to reopen my app by doing this.
Because afther that, when I click on the push notification called Kluox (and this is supposed to reopen my app), the app crashes and I really don't know why, I don't know where to put breakpoints to be able to debug because Visual Studio just tells me "An unhandled exception occured.".
Could anyone help me ? If you need any piece of code, you can just ask me, I'll edit my message and give you any information you need !
EDIT 1 : Here is the code of my OnCreate method :
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
var info = Intent.Extras?.GetString("CommandId", "");
global::Xamarin.Forms.Forms.Init(this, bundle);
if (String.IsNullOrEmpty(info))
{
LoadApplication(new App());
}
else
{
LoadApplication(new App(info));
}
if (instance == null)
{
instance = this;
RegisterWithGCM();
}
else
{
instance = this;
}
}
After overriding all the methods of MainActivity, I finally found the cause of the crash : the method OnDestroy was called twice, and threw a IllegalStateException because the activity was already destroyed. I found this workaround :
protected override void OnDestroy()
{
try
{
base.OnDestroy();
}
catch (Java.Lang.IllegalStateException ex)
{
Log.Debug("MainActivity.OnDestroy", ex, "The activity was destroyed twice");
}
}
And the exception is simply logged, the application can open and be used without problems.
I'll edit this answer when the redirection works too.
EDIT : how to redirect to a page
First, we needed to register for the MessagingCenter, in the constructor
public static MyPackage.Model.Command CurrentCommand { get; set; }
public App()
{
InitializeComponent();
MainPage = new ContentPage();
MessagingCenter.Subscribe<object, DataLib.Model.Command>(this, "Command", (sender, arg) => {
try
{
CurrentCommand = arg;
}
catch(Exception ex)
{
CurrentCommand = null;
}
});
}
And send the message when we get the push notification :
private void OnMessage(string serializedCommand)
{
//stuff happens
MessagingCenter.Send<object, MyPackage.Model.Command>(this, "Command", command);
}
Finally, when we get the OnStart() of App.Xaml.cs
if (CurrentCommand != null)
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(CurrentCommand, service))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
}
For now, it seems to do the trick ! More debugging will follow, but the code seems to work. Thanks a VERY lot to #BraveHeart for their help !
well luckily for you I was there few days ago and lost a lot of hair till I got it working in Android (and still in the strugle for iOS).
When you kill your app and instantiate it again form the icon or from the notification in both cases you will go to the main activity .
If we want to take some information in the main activity from the notification that instantiated it we do it like this in OnCreate():
var info = Intent.Extras?.GetString("info", "");
Now in your case I would add extra information to the notification showing that which View/Page this notification is about, something like the name of it for example)
This extra piece of information you can pass it to the constructor of the App before you load it.
In the constructor of the app you can check if there are extra info or not , if not that means to start the app's mainPage is the default MainPage, otherwise it is a certain page.

Global Exception Handling in Xamarin Cross platform

Can you please let me know how to handle the global exception(without App crash) in Xamarin Cross platform project.
There isn't a 'Xamarin.Forms' way of doing it that I know of. You need to hook into Android and iOS, what you could do is create one method that handles them both in the same way.
A nice post about this is by Peter Norman. He describes that to implement it in Android you can do this in your MainActivity.cs.
// In MainActivity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
Xamarin.Forms.Forms.Init(this, bundle);
DisplayCrashReport();
var app = new App();
LoadApplication(app);
}
‪#‎region‬ Error handling
private static void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
LogUnhandledException(newExc);
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
LogUnhandledException(newExc);
}
internal static void LogUnhandledException(Exception exception)
{
try
{
const string errorFileName = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // iOS: Environment.SpecialFolder.Resources
var errorFilePath = Path.Combine(libraryPath, errorFileName);
var errorMessage = String.Format("Time: {0}\r\nError: Unhandled Exception\r\n{1}",
DateTime.Now, exception.ToString());
File.WriteAllText(errorFilePath, errorMessage);
// Log to Android Device Logging.
Android.Util.Log.Error("Crash Report", errorMessage);
}
catch
{
// just suppress any error logging exceptions
}
}
/// <summary>
// If there is an unhandled exception, the exception information is diplayed
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private void DisplayCrashReport()
{
const string errorFilename = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var errorFilePath = Path.Combine(libraryPath, errorFilename);
if (!File.Exists(errorFilePath))
{
return;
}
var errorText = File.ReadAllText(errorFilePath);
new AlertDialog.Builder(this)
.SetPositiveButton("Clear", (sender, args) =>
{
File.Delete(errorFilePath);
})
.SetNegativeButton("Close", (sender, args) =>
{
// User pressed Close.
})
.SetMessage(errorText)
.SetTitle("Crash Report")
.Show();
}
‪#‎endregion‬
And for iOS you can add code like this in your AppDelegate.cs.
//iOS: Different than Android. Must be in FinishedLaunching, not in Main.
// In AppDelegate
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary options)
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
// Rest of your code...
}
/// <summary>
// If there is an unhandled exception, the exception information is diplayed
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private static void DisplayCrashReport()
{
const string errorFilename = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Resources);
var errorFilePath = Path.Combine(libraryPath, errorFilename);
if (!File.Exists(errorFilePath))
{
return;
}
var errorText = File.ReadAllText(errorFilePath);
var alertView = new UIAlertView("Crash Report", errorText, null, "Close", "Clear") { UserInteractionEnabled = true };
alertView.Clicked += (sender, args) =>
{
if (args.ButtonIndex != 0)
{
File.Delete(errorFilePath);
}
};
alertView.Show();
}
It also includes the ability to show the log when you're debugging the application. Of course you can implement your own logging or handling methods. One thing you could look at is HockeyApp. This handles unhandled exceptions by default and sends them back to you, amongst other things.
Update, since this is still found on Google: For crash reporting and analytics you now want to start looking at App Center. This is the evolvement of HockeyApp and Xamarin Insights (among others like building, distributing and push notifications) and now acts as the mission dashboard for everything that has to do with apps, and not just Xamarin.
For UWP and WinPhone 8.1 there should be a UnhandledException handler in the Application object. Check out this answer for more information. I quote:
For XAML-based apps, you can use UnhandledException; however, that
only captures exceptions that come up through the XAML (UI) framework
and you don't always get a lot of information about what the root
cause is, even in InnerException.
Update for Windows 8.1: UnhandledException also will capture
exceptions that are created by an async void method. In Windows 8,
such exceptions would just crash the app. LunarFrog has a good
discussion of this on their website.
Basically you should add a event handler in the constructor of your App() in App.xaml.cs: this.UnhandledException += (o, s) => {}.
This is what I did for xamarin forms Android global exception handling and app crashes.
This solution will handle both background and foreground exceptions.
AppDomain.CurrentDomain.UnhandledException += (s,e)=>
{
System.Diagnostics.Debug.WriteLine("AppDomain.CurrentDomain.UnhandledException: {0}. IsTerminating: {1}", e.ExceptionObject, e.IsTerminating);
};
AndroidEnvironment.UnhandledExceptionRaiser += (s, e) =>
{
System.Diagnostics.Debug.WriteLine("AndroidEnvironment.UnhandledExceptionRaiser: {0}. IsTerminating: {1}", e.Exception, e.Handled);
e.Handled = true;
};
As a result, I have different behavior for both types of exceptions:
foreground:
background:

Google Drive API implementation Xamarin Android

Our application should have the functionality to save Application files to Google Drive. Of course, using the local configured account.
From Android API i tried to figure out some clue. But android API with Xamarin implementation seems very tough for me.
I have installed Google Play Services- Drive from Xamarin Components but there are no examples listed from which we can refer the flow and functionality.
The basic steps (see the link below for full details):
Create GoogleApiClient with the Drive API and Scope
Try to connect (login) the GoogleApiClient
The first time you try to connect it will fail as the user has not selected a Google Account that should be used
Use StartResolutionForResult to handle this condition
When GoogleApiClient is connected
Request a Drive content (DriveContentsResult) to write the file contents to.
When the result is obtained, write data into the Drive content.
Set the metadata for the file
Create the Drive-based file with the Drive content
Note: This example assumes that you have Google Drive installed on your device/emulator and you have registered your app in Google's Developer API Console with the Google Drive API Enabled.
C# Example:
[Activity(Label = "DriveOpen", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : Activity, GoogleApiClient.IConnectionCallbacks, IResultCallback, IDriveApiDriveContentsResult
{
const string TAG = "GDriveExample";
const int REQUEST_CODE_RESOLUTION = 3;
GoogleApiClient _googleApiClient;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate
{
if (_googleApiClient == null)
{
_googleApiClient = new GoogleApiClient.Builder(this)
.AddApi(DriveClass.API)
.AddScope(DriveClass.ScopeFile)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(onConnectionFailed)
.Build();
}
if (!_googleApiClient.IsConnected)
_googleApiClient.Connect();
};
}
protected void onConnectionFailed(ConnectionResult result)
{
Log.Info(TAG, "GoogleApiClient connection failed: " + result);
if (!result.HasResolution)
{
GoogleApiAvailability.Instance.GetErrorDialog(this, result.ErrorCode, 0).Show();
return;
}
try
{
result.StartResolutionForResult(this, REQUEST_CODE_RESOLUTION);
}
catch (IntentSender.SendIntentException e)
{
Log.Error(TAG, "Exception while starting resolution activity", e);
}
}
public void OnConnected(Bundle connectionHint)
{
Log.Info(TAG, "Client connected.");
DriveClass.DriveApi.NewDriveContents(_googleApiClient).SetResultCallback(this);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RESOLUTION)
{
switch (resultCode)
{
case Result.Ok:
_googleApiClient.Connect();
break;
case Result.Canceled:
Log.Error(TAG, "Unable to sign in, is app registered for Drive access in Google Dev Console?");
break;
case Result.FirstUser:
Log.Error(TAG, "Unable to sign in: RESULT_FIRST_USER");
break;
default:
Log.Error(TAG, "Should never be here: " + resultCode);
return;
}
}
}
void IResultCallback.OnResult(Java.Lang.Object result)
{
var contentResults = (result).JavaCast<IDriveApiDriveContentsResult>();
if (!contentResults.Status.IsSuccess) // handle the error
return;
Task.Run(() =>
{
var writer = new OutputStreamWriter(contentResults.DriveContents.OutputStream);
writer.Write("Stack Overflow");
writer.Close();
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.SetTitle("New Text File")
.SetMimeType("text/plain")
.Build();
DriveClass.DriveApi
.GetRootFolder(_googleApiClient)
.CreateFile(_googleApiClient, changeSet, contentResults.DriveContents);
});
}
public void OnConnectionSuspended(int cause)
{
throw new NotImplementedException();
}
public IDriveContents DriveContents
{
get
{
throw new NotImplementedException();
}
}
public Statuses Status
{
get
{
throw new NotImplementedException();
}
}
}
Ref: https://developers.google.com/drive/android/create-file

Android Auto - Callback when user connects mobile device

In regards to Android auto how can i get a callback when user has plugged the device into a car ? I'd like to trigger an action to occur based on when the user actually connects to android auto, is it possible ?
You should be able to get USB connection broadcast. From there you will have to write your own logic to figure out Android Auto is in foreground.
(may need to introduce a slight delay in case android auto takes time to come up. Launcher seems to be part of google play service)
Here is how I did it in my services onCreate method (right form the example code):
IntentFilter filter = new IntentFilter(CarHelper.ACTION_MEDIA_STATUS);
mCarConnectionReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String connectionEvent = intent.getStringExtra(CarHelper.MEDIA_CONNECTION_STATUS);
mIsConnectedToCar = "media_connected".equals(connectionEvent);
LogHelper.i(TAG, "Connection event to Android Auto: ", connectionEvent,
" isConnectedToCar=", mIsConnectedToCar);
if(mIsConnectedToCar ) {
if(mService == null) {
bindMusicService();
if (mService != null) {
try {
initMediaMetaData();
toggleMediaPlaybackState(mService.isPlaying());
} catch (RemoteException e) {
Log.d(TAG, e.toString());
}
}
}
}
}
};
registerReceiver(mCarConnectionReceiver, filter);

Resources