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);
}
Related
I need to make a simple callback in Xamarin, to check if the network status is connected or disconnected.
I have so far been doing it with this code:
class NetworkControl : INetworkControl
{
private readonly INetworkControl.ICallback _callback;
private readonly Context _context;
private readonly NetworkBroadcastReceiver _receiver = new NetworkBroadcastReceiver();
public NetworkControl(INetworkControl.ICallback callback, Context context)
{
_callback = callback;
_context = context;
IntentFilter filter = new IntentFilter(ConnectivityManager.ConnectivityAction);
context.RegisterReceiver(_receiver, filter);
}
public INetworkControl.ICallback Callback => _callback;
public INetworkControl.NetworkStatus Status
{
get
{
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
return INetworkControl.NetworkStatus.Connected;
}
return INetworkControl.NetworkStatus.Disconnected;
}
}
}
class NetworkBroadcastReceiver : BroadcastReceiver
{
private static String TAG = "NetworkBroadcastReceiver";
public override void OnReceive(Context context, Intent intent)
{
if (ShellBridge.Instance != null)
{
if (intent.Action.Equals(ConnectivityManager.ConnectivityAction))
{
NetworkInfo ni = (NetworkInfo)intent.Extras.Get(ConnectivityManager.ExtraNetworkInfo);
if (ni.isConnected)
{
// do something if connected
ShellBridge.Instance.NetworkBridge.Callback.NetworkStatusChanged(INetworkControl.NetworkStatus.Connected);
} else
{
ShellBridge.Instance.NetworkBridge.Callback.NetworkStatusChanged(INetworkControl.NetworkStatus.Connected);
}
}
}
}
The problem is, the function ConnectivityManager.ConnectivityAction in the Intent creating is depricated, and will soon be obsolete. After searching, I found that the pendingIntent should be used for that, but I could not find any valid example of how to use it.
The closest to what I need is this:
https://stackoverflow.com/questions/58588132/how-to-use-registernetworkcallback-with-pendingintent
But, it has not all the information I need.
I need it to be all programmatically, without changing the manifest, for, my app should be a fore- and background app.
Please help, and thank you for your time.
You can take a look at NetworkCallback .
public class ConnectionStateMonitor : NetworkCallback
{
NetworkRequest networkRequest;
public ConnectionStateMonitor()
{
networkRequest = new NetworkRequest.Builder().
AddTransportType(TransportType.Cellular).
AddTransportType(TransportType.Wifi).Build();
}
public void enable(Context context) {
ConnectivityManager connectivityManager = context.GetSystemService(Context.ConnectivityService) as ConnectivityManager;
connectivityManager.RegisterNetworkCallback(networkRequest, this);
}
public override void OnAvailable(Network network)
{
//network available
}
public override void OnLost(Network network)
{
//network lost
}
}
Usage
You just need to instantiate the class ConnectionStateMonitor and enable it , you could detect the network status with the method OnAvailable and OnLost .
ConnectionStateMonitor m = new ConnectionStateMonitor ();
m.enable(context);
Refer
https://github.com/xamarin/Essentials/issues/512
ConnectivityManager.CONNECTIVITY_ACTION deprecated
You don't need to reinvent the wheel. You can achieve all that with Xamarin Essentials' Connectivity.
Besides checking if there is a connectivity like this:
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
// Connection to internet is available
}
you can also track when the connectivity type changes:
public class ConnectivityTest
{
public ConnectivityTest()
{
// Register for connectivity changes, be sure to unsubscribe when finished
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
}
void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
var access = e.NetworkAccess;
var profiles = e.ConnectionProfiles;
}
}
I'm trying to find some solutions to my problem here, but with no result (or I just do not get them right) so if anyone could help / explain i will be really gratefull.
I'm just developing a tool for system administrators using Win Form and now I need to create a continuous ping on the selected machine which is running on the background. There is an indicator for Online status on UI which I need to edit with background ping. So right now I'm in this state:
Class A (Win form):
ClassB activeRelation = new ClassB();
public void UpdateOnline(Relation pingedRelation)
{
//There is many Relations at one time, but form shows Info only for one...
if (activeRelation == pingedRelation)
{
if (p_Online.InvokeRequired)
{
p_Online.Invoke(new Action(() =>
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure
));
}
else
{
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure;
}
}
}
//Button for tunring On/Off the background ping for current machine
private void Btn_PingOnOff_Click(object sender, EventArgs e)
{
Button btn = (sender is Button) ? sender as Button : null;
if (btn != null)
{
if (activeRelation.PingRunning)
{
activeRelation.StopPing();
btn.Image = Properties.Resources.Switch_Off;
}
else
{
activeRelation.StartPing(UpdateOnline);
btn.Image = Properties.Resources.Switch_On;
}
}
}
Class B (class thats represent relation to some machine)
private ClassC pinger;
public void StartPing(Action<Relation> action)
{
pinger = new ClassC(this);
pinger.PingStatusUpdate += action;
pinger.Start();
}
public void StopPing()
{
if (pinger != null)
{
pinger.Stop();
pinger = null;
}
}
Class C (background ping class)
private bool running = false;
private ClassB classb;
private Task ping;
private CancellationTokenSource tokenSource;
public event Action<ClassB> PingStatusUpdate;
public ClassC(ClassB classB)
{
this.classB = classB;
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ping = PingAction(token);
running = true;
}
public void Stop()
{
if (running)
{
tokenSource.Cancel();
ping.Wait(); //And there is a problem -> DeadLock
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
private async Task PingAction(CancellationToken ct)
{
bool previousResult = RemoteTasks.Ping(classB.Name);
PingStatusUpdate?.Invoke(classB);
while (!ct.IsCancellationRequested)
{
await Task.Delay(pingInterval);
bool newResult = RemoteTasks.Ping(classB.Name);
if (newResult != previousResult)
{
previousResult = newResult;
PingStatusUpdate?.Invoke(classB);
}
}
}
So the problem is in deadlock when I cancel token and Wait() for task to complete -> it's still running, but While(...) in task is finished right.
You have a deadlock because ping.Wait(); blocks UI thread.
You should wait for task asynchronously using await.
So, if Stop() is event handler then change it to:
public async void Stop() // async added here
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
If it is not:
public async Task Stop() // async added here, void changed to Task
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
As mentioned by #JohnB async methods should have Async suffix so, the method should be named as StopAsync().
Similar problem and solution are explained here - Do Not Block On Async Code
You should avoid synchronous waiting on tasks, so you should always use await with tasks instead of Wait() or Result. Also, as pointed by #Fildor you should use async-await all the way to avoid such situations.
Is this possible to make an app what will recognize if I tell eg. "top" or "back" in the background and will start some actions when find if I tell that.
I tested speech recognition when click on button and it shows google voice recognition.
Can I do that without click on button, with recognition in real time in the background?
Yes it is very possible, if you are using google voice recognition on android, what you have to do to get rid of the clicking button is to make your own SpeechRecognizer class and inherit it.
Here is the code I use on my apps :
public class CustomRecognizer : Java.Lang.Object, IRecognitionListener, TextToSpeech.IOnInitListener
{
private SpeechRecognizer _speech;
private Intent _speechIntent;
public string Words;
public CustomRecognizer(Context _context)
{
this._context = _context;
Words = "";
_speech = SpeechRecognizer.CreateSpeechRecognizer(this._context);
_speech.SetRecognitionListener(this);
_speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
_speechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
_speechIntent.PutExtra(RecognizerIntent.ActionRecognizeSpeech, RecognizerIntent.ExtraPreferOffline);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, 1500);
}
void startover()
{
_speech.Destroy();
_speech = SpeechRecognizer.CreateSpeechRecognizer(this._context);
_speech.SetRecognitionListener(this);
_speechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, 1000);
_speechIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, 1500);
StartListening();
}
public void StartListening()
{
_speech.StartListening(_speechIntent);
}
public void StopListening()
{
_speech.StopListening();
}
public void OnBeginningOfSpeech()
{
}
public void OnBufferReceived(byte[] buffer)
{
}
public void OnEndOfSpeech()
{
}
public void OnError([GeneratedEnum] SpeechRecognizerError error)
{
Words = error.ToString();
startover();
}
public void OnEvent(int eventType, Bundle #params)
{
}
public void OnPartialResults(Bundle partialResults)
{
}
public void OnReadyForSpeech(Bundle #params)
{
}
public void OnResults(Bundle results)
{
var matches = results.GetStringArrayList(SpeechRecognizer.ResultsRecognition);
if (matches == null)
Words = "Null";
else
if (matches.Count != 0)
Words = matches[0];
else
Words = "";
//do anything you want for the result
}
startover();
}
public void OnRmsChanged(float rmsdB)
{
}
public void OnInit([GeneratedEnum] OperationResult status)
{
if (status == OperationResult.Error)
txtspeech.SetLanguage(Java.Util.Locale.Default);
}}
To use it on your activity, just create the class and call StartListening()
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;
}
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.