Long Running Apps on Android Wear - wear-os

The Android Wear ecosystem seems to be built around quick tasks which a user will interact with, and then close. This works great for most applications, but what about one which covers a long running task, and should not be automatically closed when the watch sleeps?
My specific case: Swing by Swing Golf GPS. The preferred operation would be to have the application remain active, and shown when the screen wakes due to user action. And the life-time of a single use will be between 2 to 4 hours.
What are some methods to go about keeping an application front and center on the Android Wear device for periods longer than a single use?

So, here is what I have come up with as a solution:
Build a notification with a PendingIntent to open the main Activity. Also pass it an intent for the delete action, so we know if the user has dismissed it.
public class SbsNotificationHelper {
private static final String NOTIFICATION_DELETED_INTENT = "sbs.notificationDeleted";
private static boolean _isNotificationActive = false;
/** Public static methods */
public static NotificationCompat.Builder buildRoundInProgressNotification(Context context) throws Throwable {
Intent viewIntent = new Intent(context, SbsRoundActivity.class);
PendingIntent viewPendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0);
context.registerReceiver(_broadcastReceiver, new IntentFilter(NOTIFICATION_DELETED_INTENT));
_isNotificationActive = true;
Intent deleteIntent = new Intent(NOTIFICATION_DELETED_INTENT);
PendingIntent deletePendintIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.circle_button, "", viewPendingIntent).build();
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.bottom_bg);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.iphone_57x57)
.setLargeIcon(bitmap)
.setContentTitle("Golf GPS")
.setContentText("Swing by Swing")
.addAction(action)
.setDeleteIntent(deletePendintIntent)
.extend(new NotificationCompat.WearableExtender()
.setContentAction(0));
return notificationBuilder;
}
public static boolean isNotificationActive() {
return _isNotificationActive;
}
/** BroadcastReceiver */
private static final BroadcastReceiver _broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
_isNotificationActive = false;
}
};
}
Use onStop() as opposed to onPause() to issue the notification. This way, if you have multiple activities in your app, you can present them (only causing onPause() of the main Activity).
#Override
protected void onStop() {
super.onStop();
int notificationId = 001;
NotificationCompat.Builder notificationBuilder = SbsNotificationHelper.buildRoundInProgressNotification(context);
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
notificationManagerCompat.notify(notificationId, notificationBuilder.build());
}
Also use the notification inside of your WearableListenerService if you communicate with an app on the handheld. Thus a notification can be popped and easily accessed when your app is opened.
#Override
public void onMessageReceived(MessageEvent messageEvent) {
super.onMessageReceived(messageEvent);
try {
if (SEND_MESSAGE_PATH.equalsIgnoreCase(messageEvent.getPath())) {
if (!SbsNotificationHelper.isNotificationActive()) {
int notificationId = 001;
NotificationCompat.Builder notificationBuilder = SbsNotificationHelper.buildRoundInProgressNotification(sbsApplication);
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
notificationManagerCompat.notify(notificationId, notificationBuilder.build());
}
}
}
catch (Throwable throwable) {
//Handle errors
}
}

the OnPause() method is called whenever the device is put to sleep or dialogue appears over the application. One or two activities can be done here, but they should be kept reasonably lightweight to prevent elongated user wait times.

I've had no problem doing a "extends Service" app on Wear device that works perfectly fine.
Basically: In your Wear app - decouple your GUI and app logic. Keep the app logic inside the service. I keep a class object that holds all the GUI data and pull it static from the service when Activity starts.
You could extend wearable service, but I use just the generic service as the center of my app and that worked perfectly fine (app runs for days without trouble).

Related

Pushing Images Vaadin Java

i am trying to create a turn-base card game in Vaadin-Java, everything was going well so far, but i have a problem with pushing Vaadin Images to other UI. I did copy Broadcast/BroadcasterView Class from Vaadin Documentation and it works as intended, but not for images.
public class Broadcaster {
static Executor executor = Executors.newSingleThreadExecutor();
static LinkedList<Consumer<String>> listeners = new LinkedList<>();
public static synchronized Registration register(
Consumer<String> listener) {
listeners.add(listener);
return () -> {
synchronized (Broadcaster.class) {
listeners.remove(listener);
}
};
}
public static synchronized void broadcast(String message) {
for (Consumer<String> listener : listeners) {
executor.execute(() -> listener.accept(message));
}
}
}
#Push
#Route("broadcaster")
public class BroadcasterView extends Div {
VerticalLayout messages = new VerticalLayout();
Registration broadcasterRegistration;
// Creating the UI shown separately
#Override
protected void onAttach(AttachEvent attachEvent) {
UI ui = attachEvent.getUI();
broadcasterRegistration = Broadcaster.register(newMessage -> {
ui.access(() -> messages.add(new Span(newMessage)));
});
}
#Override
protected void onDetach(DetachEvent detachEvent) {
broadcasterRegistration.remove();
broadcasterRegistration = null;
}
}
public BroadcasterView() {
TextField message = new TextField();
Button send = new Button("Send", e -> {
Broadcaster.broadcast(message.getValue());
message.setValue("");
});
HorizontalLayout sendBar = new HorizontalLayout(message, send);
add(sendBar, messages);
}
the code above works fine for Strings, Vaadin Icons etc, but when i replace for and naturally change the broadcast method, there is no reaction.
i've searched for the solution throughout the internet, but it seems, people don't need to push images or it's simply not possible here. I thought that this is perhaps the matter of payload, but it doesn't work even for 5px x 5px images
perhaps one of You have encountered such problem and found solution?
You need to pass data through the broadcaster, but what you write about your attempts makes me suspect that you've been trying to pass UI components (i.e. instances of com.vaadin.flow.component.html.Image). That won't work because a UI component instance cannot be attached to multiple locations (i.e. multiple browser windows in this case) at the same time.
What you can try is to pass the data (e.g. a String with the image URL) through the broadcaster and then let each subscriber create their own Image component based on the data that they receive.

BroadcastReceiver does not work when application is in background or killed

I have created BroadcastReceiver where I detect when an incoming call is received on the device.
My code is
[BroadcastReceiver()]
[IntentFilter(new[] { "android.intent.action.PHONE_STATE" })]
public class MyBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
//My implementation
}
}
Problem is when the application is not running or killed forcefully, BroadcastReceiver class does not get called.
Can anyone please help?
BroadcastReceiver does not work when application is not in background or killed
You could refer to: Android Broadcast Receiver not working when the app is killed, as CommonsWare said:
Once onReceive() returns, if you do not have an activity in the foreground and you do not have a running service, your process importance will drop to what the documentation refers to as a "cached process". Your process is eligible to be terminated at any point. Once your process is terminated, your BroadcastReceiver goes away. Hence, your code as written will be unreliable, as your process might be terminated within your 30-second window.
Solution:
So you could use a Service to implement your feature, here is my simple demo:
[Service]
[IntentFilter(new String[] { "com.xamarin.DemoService" })]
public class DemoService : Service
{
private static DemoReceiver m_ScreenOffReceiver;
public override IBinder OnBind(Intent intent)
{
return null;
}
public override void OnCreate()
{
registerScreenOffReceiver();
base.OnCreate();
}
public override void OnDestroy()
{
UnregisterReceiver(m_ScreenOffReceiver);
m_ScreenOffReceiver = null;
base.OnDestroy();
}
//From this thread: https://stackoverflow.com/questions/20592366/the-process-of-the-service-is-killed-after-the-application-is-removed-from-the-a
public override void OnTaskRemoved(Intent rootIntent)
{
Intent restartServiceIntent = new Intent(Application.Context, typeof(DemoService));
restartServiceIntent.SetPackage(PackageName);
PendingIntent restartServicePendingIntent = PendingIntent.GetService(Application.Context, 1, restartServiceIntent, PendingIntentFlags.OneShot);
AlarmManager alarmService = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService);
alarmService.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1000, restartServicePendingIntent);
base.OnTaskRemoved(rootIntent);
}
private void registerScreenOffReceiver()
{
m_ScreenOffReceiver = new DemoReceiver();
IntentFilter filter = new IntentFilter("com.xamarin.example.TEST");
RegisterReceiver(m_ScreenOffReceiver, filter);
}
}
Update:
If you need the service run at a higher priority to avoid it be killed, you could try using Foreground Service. As SushiHangover said:
As a foreground service it has a higher priority so the OS will consider it last to be killed, it avoids the automatic dozing of your services to save battery in later APIs , etc... The "downside" is the user must be made aware that it is running, thus the requirement to be placed in the notification bar, personally I do not see that as a problem and wish it was a hard requirement for all services.
BroadcastReceiver will not work when the application is killed, you have to use service for that , you can use BroadcastReceiver inside a service to run it even when the app is not running.

Xamarin Android Share Link/Text via social media from custom renderer

I wan't to share a link via social media from custom renderer
public class CustomActions : ICustomActions
{
Context context = Android.App.Application.Context;
public void ShareThisLink()
{
Intent sharingInt = new Intent(Android.Content.Intent.ActionSend);
sharingInt.SetType("text/plain");
string shareBody = "https://www.google.com";
sharingInt.PutExtra(Android.Content.Intent.ExtraSubject, "Subject");
sharingInt.PutExtra(Android.Content.Intent.ExtraText, shareBody);
context.StartActivity(Intent.CreateChooser(sharingInt, "Share via"));
}
}
This error occur
Android.Util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
even when I added the below code I still get same error
sharingInt.AddFlags(ActivityFlags.NewTask);
The problem is that Intent.CreateChooser creates yet another Intent. What you want to do is to set the flag on this new intent:
public void ShareThisLink()
{
Intent sharingInt = new Intent(Android.Content.Intent.ActionSend);
sharingInt.SetType("text/plain");
string shareBody = "https://www.google.com";
sharingInt.PutExtra(Android.Content.Intent.ExtraSubject, "Subject");
sharingInt.PutExtra(Android.Content.Intent.ExtraText, shareBody);
var intent = Intent.CreateChooser(sharingInt, "Share via");
intent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(intent);
}
Alternatively to avoid the need to do this, you could cache the MainActivity instance Xamarin.Forms uses:
public MainActivity
{
public static MainActivity Instance {get;private set;}
protected override void OnCreate(Bundle bundle)
{
Instance = this;
...
}
}
And then use the Instance as the Context in your code instead of the Application.Context

How to monitor the changes in the Bluetooth Connectivity state changes from Xamarin.Forms

Need to check the bluetooth connection to a remote device exists or got disconnected. Its basically a Forms which mainly targets Android and UWP.
I tried with the Dependency services and made the implementation in Android as below,
_[assembly: Xamarin.Forms.Dependency(typeof(BluetoothListenerActivity))]
namespace demotool.Droid
{
public class BluetoothListenerActivity : Activity,IBluetoothListener
{
public event EventHandler OnDeviceDisconnected;
public static BluetoothListenerActivity mySelf;
//string device;
public void start()
{
mySelf = this;
BluetoothStatusBroadCast mreceiver = new BluetoothStatusBroadCast();
IntentFilter mfilter = new IntentFilter(BluetoothDevice.ActionAclDisconnected);
Forms.Context.RegisterReceiver(mreceiver,mfilter);
}
public void receivedstatuschangd(string devicename,string state)
{
OnDeviceDisconnected(this, new DeviceDisconnectedEventArgs(name: devicename,status: state));
}
}
}_
BroadcastReceiver:
namespace Demo.Droid
{
[BroadcastReceiver]
class BluetoothStatusBroadCast : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
BluetoothDevice device =(BluetoothDevice)intent.GetParcelableExtra(BluetoothDevice.ExtraDevice);
BluetoothListenerActivity.mySelf.receivedstatuschangd(device.Name, intent.Action);
}
}
}
Xamarin Forms Part:
_ protected override void OnStart()
{
IBluetoothListener bluetoothlistener = DependencyService.Get();
bluetoothlistener.start();
bluetoothlistener.OnDeviceDisconnected += Bluetoothlistener_OnDeviceDisconnected;
}
private void Bluetoothlistener_OnDeviceDisconnected(object sender, DeviceDisconnectedEventArgs e)
{
Page page1 = new Page();
page1.DisplayAlert(e.Name+ " " +e.Status, "Alert", "OK");
}_
The Intent Action that I have registered- BluetoothDevice.ActionAclDisconnected, is getting triggered once the Pairing is completed or a connection request is made, which I assume is not the actual Disconnection of the devices
Is there any common plugin which monitors the Bluetooth Connectivity Changes to a remote device. Or could you please tell me the actual Intent Action that I should listen for.
Thanks in Advance !

How to use ranging in didRangeBeaconsInRegion

-------EDIT 2--------
Still using the post of Davidgyoung and these comments, now I have a FatalException :
E/AndroidRuntime﹕ FATAL EXCEPTION:
IntentService[BeaconIntentProcessor]
Process: databerries.beaconapp, PID: 19180
java.lang.NullPointerException
at databerries.beaconapp.MyApplicationName.didEnterRegion(MyApplicationName.java:76)
at org.altbeacon.beacon.BeaconIntentProcessor.onHandleIntent(BeaconIntentProcessor.java:83)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.os.HandlerThread.run(HandlerThread.java:61)
This error is due to the calling of setRangeNotifier()?
-------EDIT--------
After the post of Davidgyoung and these comments, I tried this method, but still not working :
public class MyApplicationName extends Application implements BootstrapNotifier {
private static final String TAG = ".MyApplicationName";
private RegionBootstrap regionBootstrap;
private BeaconManager beaconManager;
List region_list = new ArrayList();
#Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "App started up");
// wake up the app when any beacon is seen (you can specify specific id filers in the parameters below)
List region_list = myRegionList();
regionBootstrap = new RegionBootstrap(this, region_list);
BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this);
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
beaconManager.setBackgroundScanPeriod(3000l);
beaconManager.setBackgroundBetweenScanPeriod(5000l);
}
#Override
public void didDetermineStateForRegion(int arg0, Region arg1) {
// Don't care
}
#Override
public void didEnterRegion(Region region) {
Log.d(TAG, "Got a didEnterRegion call");
// This call to disable will make it so the activity below only gets launched the first time a beacon is seen (until the next time the app is launched)
// if you want the Activity to launch every single time beacons come into view, remove this call.
regionBootstrap.disable();
Intent intent = new Intent(this, MyActivity.class);
// IMPORTANT: in the AndroidManifest.xml definition of this activity, you must set android:launchMode="singleInstance" or you will get two instances
// created when a user launches the activity manually and it gets launched from here.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
String zone = region.toString();
Log.d(TAG, "Enter in region");
String text = "Enter in " + zone;
Log.d(TAG, text);
String uuid = "UUID : " + region.getId1();
Log.d(TAG, uuid);
//This part is not working
beaconManager.setRangeNotifier(this);
beaconManager.startRangingBeaconsInRegion(region);
}
#Override
public void didExitRegion(Region arg0) {
// Don't care
}
The errors are about the input in setRangeNotifier and an exception for startRangingBeaconsInRegion
This isn't my main class, my main class :
public class MyActivity extends Activity{
public final static String EXTRA_MESSAGE = "com.example.myapp.MESSAGE";
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d("myActivity","onCreate");
}
}
I want all the IDs of the beacons in the region. With this method if I understand, the app is wake-up in the background when she detected a region and normally the "startRangingBeaconsInRegion" can give me a list of beacons with this one I can take the Ids.
-------Original--------
I would like to know all the beacon around me. I know the UUID of this beacons and I can get it with 'region.toString();'. But, I need the others id of the beacons. And, I don't have "Beacon" on didRangeBeaconsInRegion.
How to know the beacons in the region?
And last question, it's possible to make that in the background?
Thanks
You can see an example of ranging for beacons in the "Ranging Sample Code" section here: http://altbeacon.github.io/android-beacon-library/samples.html
This will allow you to read all the identifiers by looking at each Beacon object returned in the Collection<Beacon> beacons in the callback. Like this:
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
for (Beacon beacon: beacons) {
Log.i(TAG, "This beacon has identifiers:"+beacon.getId1()+", "+beacon.getId2()+", "+beacon.getId3());
}
}
Once you start ranging, it will continue to do so in the background, provided you don't exit the activity that starts the ranging. Under some uses of the library, ranging slows down in the background, but this only happens if using the BackgroundPowerSaver class. If you don't want ranging to slow down in the background, simply don't enable background power saving with the library.

Resources