Send Data from a Service to BroadcastReceiver of other Activity (Xamarin Android) - c#

I have a problem with sending Location from service to custom BroadcastReceiver.
This is my BroadcastReceiver.cs
[BroadcastReceiver]
class MyBroadcastReceiver : BroadcastReceiver
{
public static readonly string GRID_STARTED = "GRID_STARTED";
public event EventHandler<OnLocationChangedEventArgs> mOnLocationChanged;
private Location location;
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
Toast.MakeText(context, "Grid Started", ToastLength.Short).Show();
//location = JsonConvert.DeserializeObject<Location>(intent.GetStringExtra("location"));
//mOnLocationChanged.Invoke(this, new OnLocationChangedEventArgs(location));
}
}
}
If I UNCOMMENT two lines in the upper code my app suddenly stops. I cannot tell you what is the error because, while developing Xamarin apps debugging is stopped by an internal error (I read about it on Xamarin Forums but couldn't find time to deal with it).
This is what I have done in service:
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("location",JsonConvert.SerializeObject(location));
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}
I'm using Newtonsoft.Json for sending an objet.
Any help would be appreciated.
UPDATE:
Ok, somehow I managed to reveal the error:
Unable to find a constructor to use for type
Android.Location.Location. A class should either have a default
constructor,one constructor with arguments or a constructor marked
with JsonConstructor attribute.
UPDATE:
Whole service code:
using Newtonsoft.Json;
namespace GoogleMaps
{
public class OnLocationChangedEventArgs
{
Location location;
public Location Location
{
get { return location; }
set { location = value; }
}
public OnLocationChangedEventArgs(Location location)
{
this.location = location;
}
}
[Service]
class MyService : Service
{
private LocationManager locationManager = null;
public MyService()
{
}
private class MyLocationListener : Java.Lang.Object,ILocationListener
{
Location mLastLocation;
public event EventHandler<OnLocationChangedEventArgs> onLoc;
public MyLocationListener(String provider)
{
mLastLocation = new Location(provider);
}
public void OnLocationChanged(Location location)
{
try
{
mLastLocation.Set(location);
onLoc.Invoke(this, new OnLocationChangedEventArgs(mLastLocation));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
{
}
}
private MyLocationListener locationListener = new MyLocationListener("network");
public override IBinder OnBind(Intent intent)
{
return null;
}
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("location",JsonConvert.SerializeObject(location));
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnCreate()
{
try
{
base.OnCreate();
InitializeLocationManager();
locationManager.RequestLocationUpdates(LocationManager.NetworkProvider, 0, 0, locationListener);
locationListener.onLoc += MyService_onLoc;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void MyService_onLoc(object sender, OnLocationChangedEventArgs e)
{
BroadcastStarted(e.Location);
}
public override void OnDestroy()
{
base.OnDestroy();
locationManager.RemoveUpdates(locationListener);
}
private void InitializeLocationManager()
{
if (locationManager == null)
{
locationManager = (LocationManager)GetSystemService(LocationService);
}
}
}
}
UPDATE:
This is what I told in 6th comment:
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
try
{
Toast.MakeText(context, "Grid Started", ToastLength.Short).Show();
a = new LatLng(intent.GetDoubleExtra("latitude",0),intent.GetDoubleExtra("longitude",0));
mOnLocationChanged.Invoke(this, new OnLatLngChangedEventArgs(a)); // NULL EXCEPTION LINE
}
catch (Exception ex)
{
Toast.MakeText(context, ex.Message, ToastLength.Short).Show();
}
}
}
Why is event handler mOnLocationChanged equal to null?
And service's part:
private void BroadcastStarted(Location location)
{
Intent BroadcastIntent = new Intent(this, typeof(MyBroadcastReceiver));
BroadcastIntent.PutExtra("latitude",location.Latitude);
BroadcastIntent.PutExtra("longitude", location.Longitude);
BroadcastIntent.SetAction(MyBroadcastReceiver.GRID_STARTED);
BroadcastIntent.AddCategory(Intent.CategoryDefault);
SendBroadcast(BroadcastIntent);
}

Send data (not object) from Service (using SendBroadcast) to BroadcastReceiver (in MainActivity):
Android-java Gist here. (100% working and tested code).
C# equivalent Service Class code:
(see import statements in a gist for required namespaces/classes)
[Service]
public class BackgroundService : Service
{
private static LocationReceiver mTickReceiver;
public BackgroundService()
{
}
public override IBinder OnBind(Intent arg0)
{
return null;
}
public override StartCommandResult OnStartCommand (Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnCreate()
{
registerReceiver();
}
public override void OnDestroy()
{
UnregisterReceiver(mTickReceiver);
mTickReceiver = null;
}
private void registerReceiver()
{
mTickReceiver = new LocationReceiver();
IntentFilter filter = new IntentFilter(Android.Content.Intent.ActionTimeTick); // this will broadcast Intent every minute
RegisterReceiver(mTickReceiver, filter);
}
// you can write this class in separate cs file
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionTimeTick })]
public class LocationReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
// sample data, you should get your location here,
// one way is to implement location logic in this class
double SampleLatitude=52.01566;
double SampleLongitude=65.00487;
// assuming above coordinates are from some location manager code
Intent I = new Intent();
i.SetAction("LocationData");
i.PutExtra("Latitude", SampleLatitude);
i.PutExtra("Longitude", SampleLongitude);
// PREPARE BROADCAST FOR MAINACTIVITY
SendBroadcast(i); // this broadcast will be received by mainactivity
}
}
}
C# equivalent MainActivity Class code:
(see import statements in a gist for required namespaces/classes)
public class MainActivity : AppCompatActivity
{
protected override Void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(R.layout.activity_main);
Intent i = new Intent(this, typeof(BackgroundService));
StartService(i);
IntentFilter filter = new IntentFilter("LocationData");
RegisterReceiver(new MyBroadcastReceiver(), filter);
}
// public static variables of MainActivty can be accessed and manipulated in this class
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { "LocationData" })]
class MyBroadcastReceiver : BroadcastReceiver
{
public override Void OnReceive(Context context, Intent intent)
{
// GET BROADCAST FROM RECEIVER IN THE BACKGROUND SERVICE CLASS
if (intent.GetAction() == "LocationData")
{
double lat=intent.GetDoubleExtra("Latitude", 0);
double lng=intent.GetDoubleExtra("Longitude", 1);
String LocationDataFromService=lat+","+lng;
// REPLACE this with console.writeline
Log.d("LocationDataFromService",LocationDataFromService);
}
}
}
}
In AndroidManifest.xml declare the service as:
<service android:name=".BackgroundService">
</service>
It may still throw some errors. Hope this helps.

You can also implement interface in MyBroadcastReceiver.cs.I think its easier way.
Here is the code:
MyBroadcastReceiver.cs
[BroadcastReceiver]
class MyBroadcastReceiver : BroadcastReceiver
{
public interface LocationDataInterface
{
void OnLocationChanged(LatLng point);
}
public static readonly string GRID_STARTED = "GRID_STARTED";
private LocationDataInterface mInterface;
private LatLng a;
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == GRID_STARTED)
{
try
{
// data you got from background service
a = new LatLng(intent.GetDoubleExtra("latitude",0), intent.GetDoubleExtra("longitude",0));
mInterface = (LocationDataInterface)context;
mInterface.OnLocationChanged(a);
}
catch (Exception ex)
{
Toast.MakeText(context, ex.Message, ToastLength.Short).Show();
}
}
}
}
MainActivity.cs
public class MainActivity : Activity, MyBroadcastReceiver.LocationDataInterface
{
...
public void OnLocationChanged(LatLng point)
{
// textview where you want to show location data
locationText.Text += point.Latitude + "," + point.Longitude;
// things that you want to do with location point
}
}
If there are some problems with this approach, feel free to comment it.

Related

Detect plugged & unplugged event on headphone jack in Xamarin.Android

I'm trying to find a way to detect the plugged/unplugged event on headphone Jack in Xamarin.Android. Is there a way to do it in this? and if it is, how can I implement that functionality?
you can try to convert java code into c#, for example:
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionHeadsetPlug })]
public class MyBroadcastReceiver : BroadcastReceiver
{
bool Microphone_Plugged_in = false;
public override void OnReceive(Context context, Intent intent)
{
String action = intent.Action;
int iii;
if (Intent.ActionHeadsetPlug.Equals(action))
{
iii = intent.GetIntExtra("state", -1);
if (iii == 0)
{
Microphone_Plugged_in = false;
Toast.MakeText(context, "microphone not plugged in", ToastLength.Long).Show();
}
if (iii == 1)
{
Microphone_Plugged_in = true;
Toast.MakeText(context, "microphone plugged in", ToastLength.Long).Show();
}
}
}
}
And usage:
BroadcastReceiver broadcastReceiver;
IntentFilter receiverFilter;
and initialize value in method OnCreate of your activity:
broadcastReceiver = new MyBroadcastReceiver();
receiverFilter = new IntentFilter(Intent.ActionHeadsetPlug);
And RegisterReceiver and UnregisterReceiver by override method OnResume and OnPause
protected override void OnResume()
{
base.OnResume();
RegisterReceiver(broadcastReceiver, receiverFilter);
}
protected override void OnResume()
{
base.OnResume();
IntentFilter receiverFilter = new IntentFilter(Intent.ActionHeadsetPlug);
RegisterReceiver(broadcastReceiver, receiverFilter);
}
For more, you can check thread: Detecting whether a headset is plugged into an Android device or not.
https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/broadcast-receivers
You have to write the code in xamarin.android file. Use broadcast receiver for implementation. In onReceive() you will get the intent as Intent.ACTION_HEADSET_PLUG.
Following pseudo code might help you,
public class MainActivity extends AppCompatActivity {
BroadcastReceiver broadcastReceiver;
boolean Microphone_Plugged_in = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
int iii;
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
iii = intent.getIntExtra("state", -1);
if (iii == 0) {
Microphone_Plugged_in = false;
Toast.makeText(getApplicationContext(), "microphone not plugged in", Toast.LENGTH_LONG).show();
}
if (iii == 1) {
Microphone_Plugged_in = true;
Toast.makeText(getApplicationContext(), "microphone plugged in",
Toast.LENGTH_LONG).show();
}
}
}
};
IntentFilter receiverFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
registerReceiver(broadcastReceiver, receiverFilter);
}
}
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionHeadsetPlug })]
public class HeadsetPlugBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
String action = intent.Action;
if (Intent.ActionHeadsetPlug.Equals(action))
{
var state = intent.GetIntExtra("state", -1);
if (state == 0)
{
// Plugged out
}
if (state == 1)
{
// Plugged in
}
}
}
}

Xamarin.Forms Firebasepushnotification plugin OnNotificationOpened event causes app to restart for android

I'm trying to handle a notifications opening on android that I've sent through Google's FCM on the SharedProject level by using "Plugin.FirebasePushNotification" plugin and subscribing to "OnNotificationOpened" event.
When I try to open the notification while the app is in the background I'm able to hit a breakpoint inside "OnNotificationOpened" event but after the work inside the event ends the App const. is hit and the application restarts.
I want the application to continue from its last state and hit "OnResume" method that is located inside the App class.
I've done some research about it online and almost all of them were about how splash activity might cause this issue so I've disabled my splash activity by turning the whole "SplashActivity.cs" file into a comment except the namespace and usages and implemented the wanted features of splashactivity(style) inside the "MainActivity" class after that I made sure my "MainActivity" class was the only activity that had it's "MainLauncher" attribute set to true but the problem continues.
Here are the relevant classes:
FirebaseReg.cs
public class FirebaseReg
{
public static void FirebaseInit()
{
CrossFirebasePushNotification.Current.OnTokenRefresh += (s, p) =>
{
System.Diagnostics.Debug.WriteLine($"TOKEN : {p.Token}");
CrossFirebasePushNotification.Current.Subscribe("defaultTopic");
};
CrossFirebasePushNotification.Current.OnNotificationOpened += (s, p) =>
{
try
{
//Breakpoint hits here and I'm able to finish all my work without any exceptions
}
catch (Exception ex)
{
}
};
CrossFirebasePushNotification.Current.OnNotificationAction += (s, p) =>
{
};
CrossFirebasePushNotification.Current.OnNotificationReceived += Current_OnNotificationReceived;
}
private static void Current_OnNotificationReceived(object source, FirebasePushNotificationDataEventArgs e)
{
//DO WORK
}
}
App.xaml.cs
public partial class App : Application
{
public static Uri ServerUri = new Uri(" ");
public static bool IsInForeground { get; set; } = false;
public App()
{
try
{
InitializeComponent(); //After the work in OnNotificationOpened ends breakpoint goes here
Application.Current.UserAppTheme = OSAppTheme.Unspecified;
Application.Current.MainPage = new LoginPage();
}
catch (Exception ex)
{
}
}
protected override void OnStart()
{
IsInForeground = true;
}
protected override void OnSleep()
{
IsInForeground = false;
}
protected override void OnResume()
{
IsInForeground = true;
}
}
MainActivity.cs
[Activity(Label = "TestProject1", Theme = "#style/MyTheme.Splash", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.Window.RequestFeature(WindowFeatures.ActionBar);
// Name of the MainActivity theme you had there before.
// Or you can use global::Android.Resource.Style.ThemeHoloLight
base.SetTheme(Resource.Style.MainTheme);
base.OnCreate(savedInstanceState);
Rg.Plugins.Popup.Popup.Init(this);
UserDialogs.Init(this);
Xamarin.FormsGoogleMaps.Init(this, savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
FirebasePushNotificationManager.ProcessIntent(this ,Intent);
LoadApplication(new App());
}
protected override void OnNewIntent(Intent intent)
{
FirebasePushNotificationManager.ProcessIntent(this, intent);
base.OnNewIntent(intent);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
MainApplication.cs
[Application]
public class MainApplication : Application, Application.IActivityLifecycleCallbacks
{
public MainApplication(IntPtr handle, JniHandleOwnership transer) : base(handle, transer)
{
}
public override void OnCreate()
{
base.OnCreate();
RegisterActivityLifecycleCallbacks(this);
if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
FirebasePushNotificationManager.DefaultNotificationChannelId = "DefaultChannel";
FirebasePushNotificationManager.DefaultNotificationChannelName = "defaultTopic";
}
#if DEBUG
FirebasePushNotificationManager.Initialize(this, true);
FirebaseRegister.FirebaseInit();
#else
FirebasePushNotificationManager.Initialize(this, false);
FirebaseRegister.FirebaseInit();
#endif
}
public override void OnTerminate()
{
base.OnTerminate();
UnregisterActivityLifecycleCallbacks(this);
}
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
CrossCurrentActivity.Current.Activity = activity;
}
public void OnActivityStopped(Activity activity)
{
}
}
From the Android docs, it states this:
If it has declared its launch mode to be "multiple" (the default) and
you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it
will be finished and re-created; for all other launch modes or if
FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to
the current instance's onNewIntent().
That's why your app is restarting.
The launchMode attribute of the activity affects how the activity is launched.
singleTop, singleTask, or singleInstance should be used to prevent the notification intent from creating a new activity instance.
The flag FLAG_ACTIVITY_NEW_TASK doesn't influence a new activity being created, but makes the launched activity the root of a new task.
For more, check:
https://developer.android.com/guide/topics/manifest/activity-element.html#lmode
https://developer.android.com/guide/components/activities/tasks-and-back-stack

android Bound Service gets frozen when screen is off

I created a very basic Service for dictionary playback using TTS (see the complete code below) and running into the same issue on all my 3 android devices (android versions 5, 7 and 8).
Gist: The app plays vocabulary entries, definitions and examples. Between each of them the app takes pause.
Symptoms:
The issue is happening mostly when I use 8 seconds for pause and the app is in the background mode (the screen is turned off). The playback simply gets frozen.
Sometimes the playback continues on its own with screen turned off after a lengthy pause sometimes being up to 20 - 30 minutes or even longer (but then the next entry is played after a very lenghty pause too, provided that we haven't activated screen). Could be some other process partly waking the phone?
Also, playback continues straight after I pressed Power button and screen turns on.
Debug info:
I was reckoning to press pause in Visual Studio after the app got frozen in order to see which bit of code is the cause - unfortunately the debugger seems to keep the device awake and this issue is extremely difficult to reveal.
In order to prevent my app from being frozen I acquire Partial WakeLock in my service (but this still doesn't help, even though app manifest contains permission for WAKE_LOCK)
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
My app also has Play/Pause button and I use TaskCompletionSource for the app to wait until I resume playback
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
Then, before each next word/phrase is about to be played I use the following code for my app to wait for my resuming playback
await AppSuspended.Task;
Complete code
[Service(Name = "com.my_app.service.PlaybackService")]
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
{
public IBinder Binder { get; private set; }
private Java.Util.Locale Lang;
private bool Playing;
private int EntryIndex;
private int DefinitionIndex;
private DictionaryDto Dictionary;
private EntryDto CurrentEntry;
private DefinitionDto CurrentDefinition;
private TaskCompletionSource<bool> AppSuspended;
protected TextToSpeech Tts;
private TaskCompletionSource<bool> PlaybackFinished;
private WakeLock WakeLock;
public override void OnCreate()
{
base.OnCreate();
Tts = new TextToSpeech(this, this);
Lang = Tts.DefaultLanguage;
AppSuspended = new TaskCompletionSource<bool>();
AppSuspended.TrySetResult(true);
}
public override IBinder OnBind(Intent intent)
{
Binder = new PlaybackBinder(this);
return Binder;
}
public override bool OnUnbind(Intent intent)
{
return base.OnUnbind(intent);
}
public override void OnDestroy()
{
Binder = null;
base.OnDestroy();
}
void TextToSpeech.IOnUtteranceCompletedListener.OnUtteranceCompleted(string utteranceId)
{
if (utteranceId.Equals("PlaybackFinished")) { PlaybackFinished.TrySetResult(true); }
}
void TextToSpeech.IOnInitListener.OnInit(OperationResult status)
{
// if we get an error, default to the default language
if (status == OperationResult.Error)
Tts.SetLanguage(Java.Util.Locale.Default);
// if the listener is ok, set the lang
if (status == OperationResult.Success)
{
Tts.SetLanguage(Lang);
Tts.SetOnUtteranceCompletedListener(this);
}
}
public async Task Play(string text)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.Speak(text, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
public async Task PlaySilence(long ms)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.PlaySilence(ms, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
private async Task PlayDictionary(MainActivity activity)
{
EntryIndex = 0;
for (; EntryIndex < Dictionary.Entries.Count;)
{
CurrentEntry = Dictionary.Entries.ElementAt(EntryIndex);
await AppSuspended.Task;
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(CurrentEntry.Text);
}
DefinitionIndex = 0;
for (; DefinitionIndex < CurrentEntry.Definitions.Count();)
{
CurrentDefinition = CurrentEntry.Definitions.ElementAt(DefinitionIndex);
await PlayDefinition();
await PlayExamples();
DefinitionIndex++;
}
if (Playing)
{
DefinitionIndex++;
}
EntryIndex++;
}
}
private async Task PlayExamples()
{
if (!Playing) { return; }
foreach (var example in CurrentDefinition.Examples)
{
if (!string.IsNullOrEmpty(example))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(example);
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(8).TotalMilliseconds);
}
}
}
}
private async Task PlayDefinition()
{
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await PlayDefinitionText();
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(7).TotalMilliseconds);
}
}
}
private async Task PlayDefinitionText()
{
await AppSuspended.Task;
await Play($"{CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text}");
}
private void ReleaseWakeLock()
{
if (WakeLock != null)
{
WakeLock.Release();
}
}
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
}
Additional info:
The issue happens on all of my devices
Galaxy C7 (Oreo)
Galaxy Tab A3 (Nougat)
Galaxy A3 (Lollipop)
I investigated the issue thoroughly and followed the recommendation to switch to Foreground Service which solved my problem perfectly.
Tested with Lollipop, Nougat, Oreo.
Foreground Service aproach
Put the following method in your MainActivity class
public void StartForegroundServiceSafely(Intent intent)
{
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
StartForegroundService(intent);
}
else
{
StartService(intent);
}
}
You then start your service via Intent
public void PlayFromFile(Android.Net.Uri uri)
{
AcquireWakeLock();
Intent startIntent = new Intent(this, typeof(PlaybackService));
startIntent.SetAction(PlaybackConsts.Start);
startIntent.PutExtra("uri", uri.ToString());
StartForegroundServiceSafely(startIntent);
}
Implement OnStartCommand method in your service
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
if (intent.Action.Equals(PlaybackConsts.Start))
{
var notification =
new Notification.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.ApplicationName))
.SetContentText("HELLO WORLD")
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
if (intent.Action.Equals(PlaybackConsts.Start))
{
var uri = Android.Net.Uri.Parse(intent.GetStringExtra("uri"));
var content = MiscellaneousHelper.GetTextFromStream(ContentResolver.OpenInputStream(uri));
Dictionary = DictionaryFactory.Get(content);
Playing = true;
Task.Factory.StartNew(async () =>
{
await PlayDictionary();
});
}
if (intent.Action.Equals(PlaybackConsts.PlayPause))
{
bool isChecked = intent.GetBooleanExtra("isChecked", false);
PlayPause(isChecked);
}
if (intent.Action.Equals(PlaybackConsts.NextEntry))
{
NextEntry();
}
if (intent.Action.Equals(PlaybackConsts.PrevEntry))
{
PrevEntry();
}
if (intent.Action.Equals(PlaybackConsts.Stop))
{
Task.Factory.StartNew(async () =>
{
await Stop();
});
StopForeground(true);
StopSelf();
}
return StartCommandResult.Sticky;
}
From the code above we've learned how to trigger service's functionality in OnStartCommand method.
How to broadcast events from Service
Define your BroadcastReceiver
[BroadcastReceiver(Enabled = true, Exported = false)]
public class PlaybackBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var activity = MainActivity.GetInstance(); // if you need your activity here, see further code below
if (intent.Action == "renderEntry")
{
string entryHtml = intent.GetStringExtra("html");
// omitting code to keep example concise
}
}
}
Declare receiver field in your MainActivity class.
Also encase you need your activity in BroadcastReceiver class you can declare GetInstance method (singleton approach).
public class MainActivity : AppCompatActivity
{
PlaybackBroadcastReceiver receiver;
protected DrawerLayout drawerLayout;
protected NavigationView navigationView;
protected WakeLock WakeLock;
private static MainActivity instance;
public static MainActivity GetInstance()
{
return instance;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
receiver = new PlaybackBroadcastReceiver();
instance = this;
}
protected override void OnStart()
{
base.OnStart();
RegisterReceiver(receiver, new IntentFilter("renderEntry"));
}
In order to unregister receiver use the following line:
UnregisterReceiver(receiver);
Broadcasting events from service
In your service you must also use intent
private void SendRenderEntryBroadcast(EntryDto entry)
{
Intent intent = new Intent("renderEntry");
intent.PutExtra("html", GetEntryHtml(entry));
SendBroadcast(intent);
}

Unable to access data from service in activity

I am building a Xamarin.Android application. I have a working service, and I am trying to get data from that service to an activity that I have. I know that people get upset over too much code being posted, but I am unsure where my issue is. I am going post what I think is necessary:
Service
[Service]
public class DataService : Service
{
public List<MyObjects> MyObjectList { get; private set; }
public IBinder Binder { get; private set; }
public override IBinder OnBind(Intent intent)
{
this.Binder = new DataServiceBinder(this);
return this.Binder;
}
public override void OnCreate()
{
base.OnCreate();
pendingIntent = PendingIntent.GetActivity(this, 0, new Intent(this, typeof(MainActivity)), 0);
StartForeground(NotificationID, GetNotification("Started Sync"));
//Do some work here to instantiate and populate the List
}
}
Service Binder
public class DataServiceBinder : Binder
{
public DataServiceBinder(DataService service)
{
Service = service;
}
public DataService Service { get; private set; }
}
Service Connection
public class DataServiceConnection : Object, IServiceConnection
{
public DataService Service { get; private set; }
public DataServiceBinder dataServiceBinder;
public bool IsConnected { get; private set; }
public void OnServiceConnected(ComponentName name, IBinder service)
{
dataServiceBinder = service as DataServiceBinder;
IsConnected = this.dataServiceBinder != null;
Service = dataServiceBinder.Service;
ServiceConnectionChanged?.Invoke(this, true);
}
public void OnServiceDisconnected(ComponentName name)
{
ServiceConnectionChanged?.Invoke(this, false);
Service = null;
}
public event EventHandler<bool> ServiceConnectionChanged;
}
Activity
public class MyDataActivity : AppCompatActivity
{
private DataServiceConnection DataServiceConnection;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (DataServiceConnection == null)
{
this.DataServiceConnection = new DataServiceConnection();
}
Intent serviceToStart = new Intent(this, typeof(DataService));
BindService(serviceToStart, this.DataServiceConnection, Bind.AutoCreate);
SetContentView(Resource.Layout.MyDataLayout);
var adapter = new MyDataAdapter(DataServiceConnection.Service.MyObjectList);
}
protected override void OnResume()
{
var intent = new Intent(this, typeof(DataService));
BindService(intent, DataServiceConnection, Bind.AutoCreate);
base.OnResume();
}
protected override void OnPause()
{
UnbindService(DataServiceConnection);
base.OnPause();
}
In my activity, when I try to pass the adapter the list from the service, the "Service" is null, and there for I get a null reference exception. Why is my Service null? Am I not binding my service properly? For the record, they service is started in the Application context.
Ok, I found the solution. Since connecting to the service is asynchronous I cannot expect the adapter to be able to get data synchronously like this:
var adapter = new MyDataAdapter(DataServiceConnection.Service.MyObjectList);
Instead, I modified my activity to look like this:
public class MyDataActivity : AppCompatActivity
{
private DataServiceConnection DataServiceConnection;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (DataServiceConnection == null)
{
this.DataServiceConnection = new DataServiceConnection();
}
Intent serviceToStart = new Intent(this, typeof(DataService));
BindService(serviceToStart, this.DataServiceConnection, Bind.AutoCreate);
DataServiceConnection.ServiceConnectionChanged += ServiceConnectionChanged;
SetContentView(Resource.Layout.MyDataLayout);
}
private void ServiceConnectionChanged(object sender, bool isConnected)
{
if(DataServiceConnection.Service == null)
{
return;
}
if(isConnected)
{
var adapter = new MyDataAdapter(DataServiceConnection.Service.MyObjectList);
}
}
protected override void OnResume()
{
var intent = new Intent(this, typeof(DataService));
BindService(intent, DataServiceConnection, Bind.AutoCreate);
base.OnResume();
}
protected override void OnPause()
{
UnbindService(DataServiceConnection);
base.OnPause();
}
I also modified my Service Connection as such:
public class DataServiceConnection : Object, IServiceConnection
{
public DataService Service { get; private set; }
public MyDataActivity DataActivity;
public event EventHandler<bool> ServiceConnectionChanged;
public DataServiceConnection(MyDataActivity myDataActivity)
{
DataActivity = myDataActivity;
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
dataServiceBinder = service as DataServiceBinder;
Service = dataServiceBinder.Service;
ServiceConnectionChanged?.Invoke(this, true);
}
public void OnServiceDisconnected(ComponentName name)
{
ServiceConnectionChanged?.Invoke(this, false);
Service = null;
}
}
Now my solution only tries to send data to the adapter when the service is connected. This solution is working for me, and I hope it helps anyone else.

How to get variable from another class to OnPrepare/CreateOptionsMenu

I have a simple wifi app. It is just scanning wifis and listing them. I figured out my ScanResult List is filling in another method but in OnPrepare/CreateOptionsMenu it is always null.
This method calls first when program starts I know that, but when i try to call it again with InvalidateOptionsMenu(); nothing changes. Here is my piec of code:
MainActivity:
public class MainActivity : Activity
{
public static Context context;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
Button wifiButton = FindViewById<Button>(Resource.Id.popupButton);
RegisterReceiver(new WiFiReceiver(), new IntentFilter(WifiManager.ScanResultsAvailableAction));
((WifiManager)GetSystemService(WifiService)).StartScan();
wifiButton.Click += (s, arg) => {
InvalidateOptionsMenu();
PopupMenu menu = new PopupMenu(this, wifiButton);
menu.Inflate(Resource.Menu.menu);
menu.Show();
};
}
public override bool OnCreateOptionsMenu(IMenu menu) {
WiFiReceiver wifiReceiver = new WiFiReceiver();
IList<ScanResult> availableWifis = wifiReceiver.GetWifiList();//This is always null
if (availableWifis != null) {
foreach (ScanResult token in availableWifis) {
menu.Add(token.Ssid);
}
}
return base.OnCreateOptionsMenu(menu);
}
}
WiFi Scanning this class:
class WiFiReceiver : BroadcastReceiver {
private IList<ScanResult> wifiList;
private string message;
public override async void OnReceive(Context context, Intent intent) {
MainActivity mainActivity = (MainActivity)context;
WifiManager wifiManager = (WifiManager)mainActivity.GetSystemService(Context.WifiService);
this.message = string.Join("\r\n", wifiManager.ScanResults
.Select(r => $"{r.Ssid} - {r.Level} dB"));
this.wifiList = wifiManager.ScanResults.ToList(); //This has connection informations
mainActivity.DisplayText(message);
await Task.Delay(TimeSpan.FromSeconds(1));
wifiManager.StartScan();
}
public IList<ScanResult> GetWifiList() {
return wifiList;
}

Categories

Resources