Android Background Bluetooth Processing: What's the best approach? - android

I've just develop an Android app (minSdkVersion 23/ targetSdkVersion 29) that can connect to a BluetoothLE device to obtain data periodically.
Now, in the MainActivity (not the first activity), I do the following registering the broadcastReciever:
public class StatusActivity extends AppCompatActivity {
BleService mBleService;
BleScanCallback mScanCallback;
BroadcastReceiver mBroadcastReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
mBroadcastReceiver = new LibBleBroadcastReceiver(this);
IntentFilter intentFilter = new IntentFilter()
intentFilter.addAction(BleService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BleService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BleService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BleService.ACTION_DATA_AVAILABLE);
intentFilter.addAction(BleService.ACTION_DID_WRITE_CHARACTERISTIC);
intentFilter.addAction(BleService.ACTION_DID_FAIL);
registerReceiver(mBroadcastReceiver, intentFilter);
mScanCallback = new LibBleScanCallback(this);
intent = new Intent(this, BleService.class);
connection = new LibBleServiceConnection(this);
startService(intent);
if (!bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
throw new IllegalStateException("bindService not successful");
}
}
...
public void onDeviceDiscovered(String device_address){
device_connected.activateNotifications(mBleService, connected_device);
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
public void run() {
device_connected.requestTemperature(mBleService, connected_device);
}
}, 0, 30, TimeUnit.MINUTES);
}
...
}
In the AndroidManifest.xml it's declared the BleService:
<service android:name=".bluetooth.BleService" android:enabled="true" />
And then, the user select a device (from a bluetooth scan) and connect to it to obtain data from BLE device.
Once is connected and discovered (services and characteristics), I schedule a task every 30 minutes to obtain data from the device.
All the callbacks that are executed when the device is connected/discovered/dataRecieved are in the StatusActivity not in the BleService (intent) to make changes in the UI (although in the background it would not be necessary).
In the other hand, I have to mantain always a bakground process too, because my app, starts a BLE Advertising to make the phone an LE Device, so always have to be "powered on" to make others devices find the phone.
The problem is that when I put the app in Background or I kill the app this schedule doesn't execute or execute a few times until the process is killed by android.
What would be the best approach to execute this service in background mode? Considering that if the app is killed, the device (I think) will be disconnected so I should connect another time to the device (I save the MAC address to reconnect so it's not a problem) and execute the requestData method? WorkManager/JobScheduler/AlarmManager/ForegroundService?
Could someone help me to know how to implement and understand the Background lifecycle and how to access all data I need to manage in background?
Thank you!

The only working solution for working in Background is ForegroundService, other ones will be destroyed when your device will enter Doze mode. You can find more detailed information in this article, it describes all the obstacles in background working while scanning BLE devices.

Related

How can I make my app running also in Doze mode without Partial_Wakelock? [duplicate]

One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Below is the code:
public class GpsTrackingService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
do{
try{
//make API call here
//then go to sleep for 2 mins
TimeUnit.SECONDS.sleep(120);
} catch(InterruptedException ex){
ex.printStackTrace();
}
} while (preferences.shouldSendGps()); //till the user can send gps.
}
....
}
Manifest
<service android:name=".commons.GpsTrackingService" />
This is working fine when the phone is active. However, whenever the phone goes into doze mode it fails to wake.
Will using alarm manager with WAKE permission solve this?
I have just got the code base and need to fix this within today. It'll be great if someone can help.
As the documentation says:
In Doze mode, the system attempts to conserve battery by restricting
apps' access to network and CPU-intensive services. It also prevents
apps from accessing the network and defers their jobs, syncs, and
standard alarms.
Periodically, the system exits Doze for a brief time to let apps
complete their deferred activities. During this maintenance window,
the system runs all pending syncs, jobs, and alarms, and lets apps
access the network.
In few words, while in Doze mode the system suspends network accesses, ignores Wake Locks, stops acquiring data from sensors, defers AlarmManager jobs to the next Doze maintenance window (which are progressively less frequently called), also WiFi scans, JobScheduler jobs and Sync adapters do not run.
Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 (?) minutes, per app.
And it seems that the Foreground Services are also involved into this "Doze Drama", at least in MarshMellow (M).
To survive in this situation, tons of applications need to be at least rewiewed. Can you imagine a simple mp3 player which stops playing music when the device enters in Doze Mode?
Doze mode starts automatically, when the device is unplugged from the power supply and left on the table for about 1 hour or so, or even earlier when the user clicks the power button to power down the screen, but I think this could depend by the device manufacturer too.
I tried a lot of countermeasures, some of them really hilarious.
At the end of my tests I reached a possible solution:
One possible (and maybe the only) way to have your app running even when the host device is in Doze mode, is basically to have a ForegroundService (even a fake one, doing no jobs at all) running in another process with an acquired partial WakeLock.
What you need to do is basically the following (you could create a simple project to test it):
1 - In your new project, create a new class which extends Application (myApp), or use the
main activity of the new project.
2 - In myApp onCreate() start a Service (myAntiDozeService)
3 - In myAntiDozeService onStartCommand(), create the Notification
needed to start the service as a foreground service, start the
service with startForeground(id, notification) and acquire the
partial WakeLock.
REMEMBER! This will work, but it is just a starting point, because you have to be careful with the "Side Effects" this approach will generate:
1 - Battery drain: The CPU will work for your app forever if you
don't use some strategy and leave the WakeLock always active.
2 - One notification will be always shown, even in the lockscreen,
and this notification cannot be removed by simply swiping it out, it
will be always there until you'll stop the foreground service.
OK, let's do it.
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
#Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
#Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml changes.
In the AndroidManifest.xml add this permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Don't forget to add the name of your app in the <application> tag:
<application
....
android:name=".myApp"
....
And finally add your foreground service running into another process:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
A couple of notes.
In the previous example, the notification created, when clicked,
opens the ActivityMain activity of your test project.
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
but you can use another kind of intent too.
To test it, you have to add some job to be performed into your
ActivityMain.java, for example some repeating alarm (which was
normally stopped when the device falls in Doze Mode), or a ripetitive
network access, or a timed tone played, or.... whatever you want.
Remember that the job performed by the main activity has to run
forever because to test this AntiDoze you need to wait at least 1
hour to be sure the device enters in Doze Mode.
To enter in Doze mode, the device has to be quiet and unplugged, so
you can't test it while you are debugging. Debug your app first,
check that everything is running then stop it, unplug, restart the
app again and leave the device alone and quiet on your desk.
The adb commands suggested by the documentation to simulate Doze
and StandBy modes could and could not give you the right results
(it depends, I suppose, by the device manufacturer, drivers, bla
bla). Please make your tests in the REAL behaviour.
In my first test, I used an AlarmManager and a tone generator to play a tone every 10 minutes just to understand that my app was still active.
And it is still running from about 18 hours, breaking my ears with a loud tone exactly every 10 minutes. :-)
Happy coding!
One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Only have a service running while it is actively delivering value to the user. Sitting around for two minutes, watching the clock tick, is not actively delivering value to the user.
Will using alarm manager with WAKE permission solve this?
That depends on what you mean by "solve this". You can use AlarmManager to request to get control every two minutes so that you can do work. While the device is in Doze mode, you will not actually get control every two minutes, but once per maintenance window.

Background operations when app is killed? (Android API 23+)

To learn something new, I'm developing an Android APP (min SDK version API 23 and Target SDK Version API 28) that allows me and my family to create and share a virtual shopping list through HTTP requests and JSON responses on a free Web. Everything works fine, but I want to add a feature: I would like to get notified when someone makes a change even when the app is killed or has never been launched. I know what the task could do to compare the changes made on the list and I also know that it is something to be done once every 5 minute (for example), but I don't know how to perform background operations when the app is no longer running and it has been killed from the recent tasks list. I gave the Service class a try, but when the app is killed it stops. So I looked for a solution and I found the BroadcastReceiver and made it able to receive a message whenever the Service stops in order to restart it. But from Android API 26 the BroadcastReceiver must be (I guess..) contex-registered.
So this is what I my main Activity does when the onCreate method is called:
ReceiverCall receiver = new ReceiverCall();
registerReceiver(receiver, new IntentFilter("com.dadex.familyapp.startServiceRequest"));
My ReceiverCall which extends the BroadcastReceiver Class:
public class ReceiverCall extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try{
String action = intent.getAction();
if (action.equals("com.dadex.familyapp.startServiceRequest"))
context.startService(new Intent(context, CheckListService.class));
}
catch (Exception e){
Log.e("ERROR", e.getMessage());
}
}
}
And this is my CheckListService onDestroy method:
#Override
public void onDestroy() {
Intent intent = new Intent("com.dadex.familyapp.startServiceRequest");
sendBroadcast(intent);
}
It works fine when the app is launched, but as soon as I kill it, the receiver won't receive anything. So my question is: what is the best way to perform such background operations? Are there other classes I need to learn first? Thanks a lot!
You need a background service, with a notification to keep it alive.
startForegorund()
Search for startForegorund with notification and you will find what you need.

Android Long Lasting MQTT connection

I am in need to initiate a connection after an application is closed - onDestroy() is called and the app is no longer visible.
MainActivity initiates a service in the
#Override
public void onCreate(Bundle savedInstanceState){
if(savedInstanceState == null) {
startService(new Intent(MainActivity.this, MqttService.class));
}
The service initiates the MQTT connection via an AsyncTask.
#Override
public int onStartCommand(Intent intent, int flags, int startId){
Notification note = createNotification();
//startForeground(20, note);
new MqttTask().execute(CONNECT_RETRIES);
return START_REDELIVER_INTENT;
}
The MQTT connection is kept alive as long as the application is kept alive because the service is also ready. The service also implements some callback methods which I am in need of use, specifically:
#Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
My goal is to allow the connection to be alive even when the application is closed, so that the user can receive messages via the connection without having the app open. I guess the idea is to reconnect when the app is destroyed
What I've tried:
1) I don't like this solution because, though it works, it provides an annoying notification for the user
startForeground(23, createNotification());
2) I've attempted to use an AlarmManager to call startService(MqttService.class) on a certain interval though according to the best practices this is not recommended.
3) I've looked at the https://developer.android.com/training/sync-adapters/creating-sync-adapter.html however this seems more a "connect-once" rather than a continued and established connection.
Any ideas?
Have you looked at the Paho Android service rather that writing your own?
https://eclipse.org/paho/clients/android/
This should run in the background and remain running until it's explicitly killed.
Also if you want to continue to use your own service then you should look at this previous question.
How to restart a killed service automatically?

How to monitor all input from a Bluetooth HID gadget

I'm trying to implement an app that (mis-) uses a Bluetooth camera shutter release gadget for a completely different purpose. Here's the gadget in question:
http://www.amazon.co.uk/iPazzPort-Bluetooth-Shutter-Android-Smartphones/dp/B00MRTFB4M
As far as I can determine this uses Bluetooth v3 and is an HID device. It apparently fires the camera app shutter by simulating "volume up" (or maybe "volume down"?). Anyway, it does seem to work quite well, although sometimes you have to press the button twice - I think that maybe the first press reestablishes Bluetooth connection and the second, and subsequent, presses then just work.
I've tested it with two different devices running Android 2.3. I do want to be backwards-compatible to that version of Android.
What I want to do is to monitor all input from this device somehow, so my app can detect when the button has been pressed and then do what it wants to use the device for. (It's a kind of panic alarm system so you can press the button to indicate you need help.)
I don't want to get involved in trying to communicate with the device via Bluetooth. Android is already doing that, and it's working, and what I've read about Bluetooth and the HID protocol makes me want to avoid it if at all possible.)
I've tried overriding onKeyDown() and onKeyUp() and dispatchKeyEvent(). Sometimes they get called, sometimes they don't. And when they get called I'm seeing unexpected keyCodes like 66 (Enter) and 8 ("1").
What I'm asking is, is there some way to monitor all input from this Bluetooth HID device, without having to get involved in Bluetooth HID protocol support?
I never found a real answer to my question as such, but fortunately I found a work-around.
This particular Bluetooth gadget always connects to the paired device, sends some text, and then disconnects. So what I'm doing is creating a BroadcastReceiver to get Bluetooth connection (and disconnect) events, and using that to activate the alarm.
// Class used to receive Bluetooth connection and disconnect events. This checks the action is as
// expected (probably unnecessary) and that a request type has been selected, and sends the
// activation message to OutBack Server if so. (Monitoring the disconnect events is probably
// unnecessary, but is done just in case that saves a situation where a connection has been
// missed, or something.)
public class BluetoothReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context androidContext, Intent androidIntent) {
String actionOrNull = androidIntent.getAction();
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(actionOrNull) ||
BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(actionOrNull)) {
Log.d(TAG, "BluetoothReceiver.onReceive() " + actionOrNull);
if (_btnActivate.isEnabled()) {
sendRequestActivationToServer();
}
}
}
}
...
// Reference to the object used to monitor Bluetooth connections and disconnections
private BluetoothReceiver _bluetoothReceiver = null;
...
// Last lifecycle method called before fragment becomes active. This is apparently the
// recommended place to register a receiver object, and is used to register a receiver object to
// monitor Bluetooth connections and disconnections.
#Override
public void onResume() {
super.onResume();
_bluetoothReceiver = new BluetoothReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
getActivity().registerReceiver(_bluetoothReceiver, intentFilter);
}
...
// First lifecycle method called when a fragment is on its way to being paused or destroyed. This
// is apparently the recommended place to unregister a receiver object, and is used to unregister
// the receiver object that monitors Bluetooth connections and disconnections.
#Override
public void onPause() {
super.onPause();
if (_bluetoothReceiver != null) {
getActivity().unregisterReceiver(_bluetoothReceiver);
_bluetoothReceiver = null;
}
}
This was inspired by this question and answer: How to receive intents for Bluetooth devices working?

Android: What should I use for running a background long-term activity?

I am developing an android application using API 10 and I'm facing some issues. My application should send every 30 minutes a UDP packet to my desktop listen server. What I want to do:
The background service should remain even if the application was closed (using back button from the device)
To automatically start service if the device was restarted.
My problems:
I can't use startForeground(), because I am using API lvl 10. It was implemented in API 11.
The application wont stay in the background.
What have I done:
public class HeartbeatService extends Service{
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("er", "Started !");
TimerTask task = new TimerTask() {
#Override
public void run() {
Log.e("err", "NBOW !");
}
};
Timer timer = new Timer();
timer.schedule(task, 1000); // every 1 sec for testing
return super.onStartCommand(intent, flags, startId);
}
}
Using logcat I see only 2 lines generated "NBOW !" with the application open.
What should I do?
public MyActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, HeartbeatService.class));
}
}
I can't use startForeground(), because I am using API lvl 10. It was implemented in API 11.
startForeground() was added in API Level 5.
My application should send every 30 minutes a UDP packet to my desktop listen server.
Use AlarmManager and an IntentService, possibly my WakefulIntentService. Not only do you not need to keep a service running all the time just to get control every 30 minutes, but doing so is wasteful and increases the likelihood that the user will take steps to prevent your app from running.
To automatically start service if the device was restarted
Use a BOOT_COMPLETED BroadcastReceiver to reschedule your AlarmManager events.
What have I done
That not only requires you to waste the user's RAM watching the clock tick, but it will not work if the device is in sleep mode (which may or may not be an issue, depending upon your requirements).

Categories

Resources