With the introduction of doze mode in Android M we have seen a number of enterprise use cases where customers want to ensure their application continues to run even though the device wants to enter a power saving mode.  It is assumed the reader is familiar with doze mode, at least at a high level.

From a consumer point of view, getting maximum battery life out of a device is frequently an ever-present consideration, so much so that a slew of snake oil “task manager” and “task killer” applications formerly gained popularity to prevent background apps but in recent Android releases Google has taken a more aggressive approach to what apps can do in the background.

Enterprise applications are written primarily to enhance user efficiency; battery consumption will always be a consideration but may be secondary or tertiary to application responsiveness or performance.

The goal of this blog is to explain your options as an application developer to give you the most control over your device’s power management and what you can do to ensure your application is always available to your users.

 

There are two fundamental reasons people might not want the device to enter a power saving mode:

  • The application should be available to respond to network requests, for example a push message.  Often Firebase Cloud Messaging (FCM) is not a suitable option for customers because the device is behind a firewall or does not have GMS services installed.
  • The application needs to do work continually and it is not acceptable for the Android OS to kill the application’s background services.

 

Before diving into the detail, it is necessary to understand the concepts around Android power management:

 

Wake Locks

Wake locks are an Android concept designed to allow an application to indicate that it wants to have the device stay on, for example the YouTube application would take a FULL_WAKE_LOCK to prevent the screen turning off whilst the user is watching a video.  A wake lock can control the status of the CPU, Screen and hardware Keyboard backlight but all but one type of wake lock can be cancelled by the end user simply by pressing the power key.  Since only PARTIAL_WAKE_LOCKs persist when the user presses the power key the remainder of this blog will be concerned exclusively with those, where the CPU continues running but the screen and hardware keyboard backlight is allowed to turn off.

Android documentation is available for the wake lock definition as well as methods to acquire and release wake locks.

 

Any application requiring a wake lock must request the android permission:

 

<uses-permission android:name="android.permission.WAKE_LOCK" />

 

You can detect any wake locks being held by applications on your device through adb:

 

adb shell dumpsys power

 

The following screenshot shows a single wake lock is on the device, it is a partial wake lock and has been given the tag ‘WakeLockExampleAppTag1’ by the application which created it:

Wake lock acquired.png

Partial wake locks will be released either when the application which created it calls release(), or the lock was only acquired for a specified time.  Note that wake locks will be automatically released when the device enters doze mode unless battery optimization is disabled for the application which acquired the lock (see later)

 

Whilst this blog is concerned primarily with Android Marshmallow, you may notice that earlier Zebra devices’ WiFi service gains its own partial wake lock, even when the WiFi policy is set to not "keep WiFi awake when the device is sleeping".  You can see any wake lock using the technique described above and should bear this in mind if wondering why your device is not sleeping when expected on earlier devices e.g. MC40.

 

WiFi Locks

WiFi locks allow an application to keep the WLAN radio awake when the user has not used the device in a while, they are frequently used in conjunction with wake locks since any application doing work in the background would likely need WLAN connectivity (e.g. downloading a large file)

 

Android documentation for the WifiLock is here and the documentation on how to acquire a WiFi lock is here.

 

As stated in the official docs, WifiLocks cannot override the user-level “Wifi-Enabled” setting, nor Airplane Mode.  For Zebra devices, we can extend this to WifiLocks not being able to override the Wi-Fi Enable / Disable setting of the WiFi CSP

 

Some other special considerations for Zebra devices:

  • Out of the box, Zebra devices come pre-loaded with the AppGallery client.  AppGallery will be holding its own WifiLock lock and since the radio is only allowed to turn off when no WifiLocks are held, the radio will not turn off by default (until the device enters doze mode).  AppGallery’s lock is defined as follows:
    • WifiLock{Pushy type=1 binder=android.os.BinderProxy@...}
  • You can disable AppGallery in a number of ways:
    • Using the MX AccessManager whitelist feature
    • Using the MX ApplicationManager to disable the application
    • Using Enterprise Home Screen’s <app_disabled> preference
    • Disabling the application manually under Settings --> Apps --> AppGallery --> App info

Note that the current package name is com.rhomobile.appgallery but that may change in the future.

  • Zebra devices also have an additional “Sleep Policy” parameter as part of the MX Wifi Manager
    • You can set the sleep policy via the Settings UI: Settings --> Wi-Fi --> (Menu) Advanced --> “Keep Wi-Fi on during sleep”.  The values are:
      • Always (the WiFi radio will not turn off when the device sleeps)
      • ‘Only when plugged in’ (the WiFi radio will not turn off provided the device is connected to power)
      • Never (the WiFi radio will be allowed to turn off when the device sleeps)
    • The sleep policy does not hold a separate WiFi lock, it is configuring the WiFi policy and is reflected in the mSleepPolicy value.
    • The device will only turn WiFi off during standby if there are no WiFi locks and it is allowed to do so according to the WiFi sleep policy
  • When the device receives a network request over WiFi, the device obviously needs to do some processing to handle the message.  Although this requires hardware support from the WiFi stack and processor, recent Zebra devices will be able to wake the processor to perform the required packet handling.  If more than simple processing is required when packets are received it may be prudent to also acquire a wake lock for the duration of that processing.  This is true of all Android M or later devices.

 

You can detect any wifi locks being held by applications on your device through adb:

 

adb shell dumpsys wifi

 

The following screenshot shows a single wifi lock is on the device (in blue) and is given the tag ‘WifiLockExampleAppTag1’ by the application which created it.  Highlighted in green is the mSleepPolicy variable which can be used to determine the WiFi sleep policy, here 0 is ‘Never’ keep WiFi on during sleep but a value of 2 would indicate ‘Always’ keep WiFi on during sleep.:

 

wifi lock.png

 

Device Configuration

Will Wi-Fi turn off when the device first sleeps?

Will Wi-Fi turn off when the device enters doze mode? *

No WiFi Locks held by any applications

Sleep policy set to ‘Always’ keep wi-fi on.

No, the WiFi radio will remain on when the device sleepsYes, the WiFi radio will remain off in doze mode.

At least 1 application holds a WiFi lock

Sleep policy set to ‘Always’ keep wi-fi on.
No, the WiFi radio will remain on when the device sleepsYes, the WiFi radio will be turned off when the device enters doze mode.

No Wi-Fi locks held by any applications

Sleep policy set to ‘Never’ keep wi-fi on
Yes, the WiFi radio will turn off when the device sleepsYes, the WiFi radio will remain off in doze mode.

At least 1 application holds a WiFi lock

Sleep policy set to ‘Never’ keep wi-fi on.
No, the WiFi radio will remain on when the device sleepsYes, the WiFi radio will be turned off when the device enters doze mode.

 

* Note that both wake locks and the “Sleep Policy” will be ignored when the device is in doze mode unless battery optimization is disabled for the application which acquired the lock (see later)

 

Battery Optimization / Doze Mode

Diving into Google’s documentation for doze mode and app standby, any developer wishing to circumvent doze mode is quickly drawn to application whitelisting.

The terminology can get confusing at times so:

 

Optimized:

  • Application is not whitelisted
  • Application is optimized
  • Battery is optimized
  • Default state for all applications other than some Google system apps

 

Not Optimized:

  • Application is whitelisted
  • Application is not optimized
  • Application may drain your device battery more quickly
  • Battery optimizations are being ignored

 

You can whitelist your application in a few different ways but all require user or manual intervention:

1. By requesting the permission

 

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

 

And sending an intent with the following action:

 

Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS

 

Doing so will present the following warning dialog to the user:

 

ignore battery optimizations.png

 

If the user presses 'YES' then the application will be whitelisted, else if the user presses ‘NO’ there will be no change.

 

Note that requesting this permission and sending this intent is likely to have your application removed from the Google PlayStore, per the following warning in Android Studio, “Use of REQUEST_IGNORE_BATTERY_OPTIMIZATIONS violates the Play Store Content Policy regarding acceptable use cases, as described in …”.  This is particularly problematic for customers planning to distribute their enterprise applications through Google’s managed play store.

 

2. By sending an intent with the following action

 

Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS

 

Doing so is just a shortcut to Settings --> (menu) Battery optimization where the user can choose to manually whitelist your application by selecting ‘All apps’, finding the application and choosing the appropriate option from the dialog

 

optimize or dont optimize.png

 

This is of course error prone and the user could select the incorrect option.  There is an API to read the whitelist state of an application so one option might be to nag the user until they accept.

 

Since this is accessing the standard Android settings UI then considerations around whether or not the user is allowed access to device settings should also be taken into account.  For example, preventing access to the Settings UI with the MX AccessManager would prevent the user from being able to whitelist the application.

 

Note that in my testing it is not a good idea to mix the two techniques, I often found the UI displaying optimized applications did not get updated after I performed step 1 even though the app itself was whitelisted.

 

3. Using ADB during provisioning you can manually edit the whitelist, though obviously your device must first have developer options enabled:

 

Add your application to the whitelist as follows:

 

adb shell dumpsys deviceidle whitelist +com.yourcompany.yourapp

 

And remove your application from the whitelist with the following command:

 

adb shell dumpsys deviceidle whitelist -com.yourcompany.yourapp

 

4. This is a feature Zebra may choose to automate in the future

 

Querying the current whitelist state

Within your application you can query the isIgnoringBatteryOptimizations() method within the PowerManager API to determine whether or not your application is currently whitelisted.   If the API returns true then your application is currently on the whitelist.

 

The code is called as follows and demonstrates asking the user to whitelist the application if it is not already whitelisted:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {
    //  Prompt the user to disable battery optimization
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
    startActivity(intent);
  }
}

 

Service Stickiness

An Android service lifecycle introduces the concept of service stickiness, i.e. a service which should remain running should return START_STICKY or START_REDELIVER_INTENT from its onStartCommand().  These were introduced way back in API level 5 and were designed primarily for where the Android OS would kill a service because it was consuming too much memory, later restarting that service.

 

START_STICKY does not circumvent doze mode or keep any kind of locks, the overlap with use cases being described in this blog comes when considering long running background services. If the application wants to perform a long running background task then it is generally a good idea to return START_STICKY or START_REDELIVER_INTENT from the onStartCommand(); for an IntentService then call setIntentRedelivery(true) in the constructor to achieve the same effect.

 

Although by following the advice on locks and whitelisting in this blog can prevent your device sleeping, it cannot avoid Android stopping your service for other reasons (e.g. memory pressure) so this case should always be considered.

 

Additional documentation is available for START_STICKY, START_REDELIVER_INTENT and START_NOT_STICKY within Android Service docs, setIntentRedelivery() is documented under Android IntentService.

 

Putting it all together

  • This blog has discussed a number of variables:
    • Android wake locks
    • Android wifi locks
    • Application whitelisting and Doze mode (battery optimization)
  • This blog has also considered a couple of use cases:
    • An application wants to listen for incoming network requests and do some work when it gets a request
    • An application wants to perform a long running background service regardless of whether the device wants to sleep.

I have written a test application to test some of the concepts described here: https://github.com/darryncampbell/WakeLock_WifiLock_Exerciser.  Whilst not officially supported by Zebra, The UI should be self-explanatory enough to perform the following operations:

test application.png

 

  • On launch, if the application is not whitelisted, ask the user if they want to whitelist it
  • A background service is used to test whether the CPU is active or not:
    • Send an HTTP POST to the specified address on that background service
      • Configure that HTTP post address
    • Emit a beep in that background service
  • A web server within the device is used to test whether the WiFi is alive or not and the device is able to respond to an HTTP GET request from a desktop computer.
    • Initialise that background server on port 8080.
  • Acquire or release a wake lock
  • Acquire or release a wifi lock
  • Show the battery optimization settings UI.  As stated earlier, I have found this technique sometimes unreliable when mixed with the automatic whitelisting request on launch.

 

Table of behaviour

Note: For brevity, the following terms should also be considered synonymous for this table:

  • Acquiring a WiFi lock and setting the sleep policy to ‘Always’ keep wi-fi on.
  • Not acquiring a WiFi lock and setting the sleep policy to ‘Never’ keep wi-fi on.

Testing was performed on a GMS TC51 running Android Marshmallow but should be consistent across all Zebra Android M and later devices.

 

Configuration

Application processing can continue?

Application can respond to network requests

Wake lock: Not acquired

Wifi lock: Not acquired

Application whitelisted: No

No.

Application will stop processing a few seconds after the screen turns off.

No.

Application will stop responding to network requests after a few seconds

Wake lock: Acquired

Wifi lock: Not acquired

Application whitelisted: No

Until doze.

Application will continue processing until doze mode kicks in but will not have WLAN access

No.

Application will stop responding to network requests after a few seconds

Wake lock: Not acquired

Wifi lock: Acquired

Application whitelisted: No

No.

Application will stop processing a few seconds after the screen turns off.

Until doze.

Application will respond to network requests until deep doze mode kicks in at which time the application will stop responding to network requests.

Wake lock: Acquired

Wifi lock: Acquired

Application whitelisted: No

Until doze.

Application will continue processing until deep doze mode kicks on.

Until doze.

Application will respond to network requests until deep doze mode kicks in at which time the application will stop responding to network requests.

Wake lock: Not acquired

Wifi lock: Not acquired

Application whitelisted: Yes

No.

Application will stop processing a few seconds after the screen turns off.

No.

Application will stop responding to network requests after a few seconds

Wake lock: Acquired

Wifi lock: Not acquired

Application whitelisted: Yes

Yes.

Application will continue processing indefinitely but will not have WLAN access

No.

Application will stop responding to network requests after a few seconds

Wake lock: Not acquired

Wifi lock: Acquired

Application whitelisted: Yes

No.

Application will stop processing a few seconds after the screen turns off.

Yes.

Application will continue responding to network requests indefinitely.

Wake lock: Acquired

Wifi lock: Acquired

Application whitelisted: Yes

Yes.

Application will continue processing indefinitely.

Yes.

Application will continue responding to network requests indefinitely.

 

Upcoming changes with Android ‘O’

Android ‘O’ is introducing a number of limitations to what applications and services can do when running in the background including background services, implicit broadcasts and device location.

Most notably for this blog, Android Services will not typically be permitted to run when their owning application is in the background.  There are exceptions to this, in particular a Service will be allowed to run temporarily to service a high-priority Firebase Cloud Messaging (FCM) message, to handle a PendingIntent addressing the service or if the service was started in response to a broadcast but after several minutes the service will be stopped by the Android system.  At this early stage, it does not appear possible to circumvent these restrictions for running in the background; work arounds provided by Android are to use a foreground service, FCM or the Job Scheduler API to execute at some point in the future, meaning applications wishing to remain running in the background will likely need to use a foreground service.  Since the service is being stopped in a similar manner to calling stopSelf() then returning START_STICKY from the onStartCommand() will not help to keep the service alive in the background.

 

Android background service limitations are fully documented by Android here and the background location limits are documented here.

The conclusions presented earlier in this blog do not take account of Android ‘O’ changes since ‘O’ is still in developer preview and the behaviour is subject to change.

As ever, Zebra will evaluate how this impacts our customers as we release device OS builds of 'O' and add additional capabilities for enterprises where appropriate.

Using Alarms whilst in Doze Mode

This document has so far focused on the ability of an application to do work and respond to network requests whilst the device is in doze mode, an additional limitation of doze mode is that the standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window, i.e. you cannot rely on the alarms firing at the exact required time.  To be clear, disabling battery optimizations for your application will NOT circumvent the alarm restrictions present when the device is in doze mode.

 

You have a number of options:

  1. Make use of the new APIs introduced in MarshMallow, setAndAllowWhileIdle and setExactAndAllowWhileIdle. These alarms will fire at the desired time even whilst the device is in doze mode but should be used sparingly to avoid excessive battery drain.  The primary restriction of these alarms is the system will throttle how often they are allowed to fire so you may not be able to trigger more than 1 alarm every 9 minutes (or 15 minutes, both values can be found in the documentation).
  2. If you want an alarm to reliably fire more frequently than every 9 minutes with the device in doze mode then you cannot use the methods detailed in 1, above.  It is recommended that the application wanting to trigger frequent alarms not use the alarm API under these circumstances but instead take a wake lock and manage a timer internally in its own thread.
  3. Use the setAlarmClock API which will wake the system from doze mode shortly before the alarm fires.  The down side of this API for many developers will be that Android displays information about the alarm to the user, typically as a small alarm clock icon in the status bar.

      clock icon.png

For more information, please see the official Google documentation for Doze mode.

 

 

Final Thoughts

Whilst Zebra will continue to support and advocate enterprise use cases which depend on the device remaining awake, any application wishing to keep the device awake will face an uphill battle since the industry as a whole, including iOS, are continuing to extend the device battery life by restricting background capabilities of applications. Where possible it is recommended to consider alternatives to keeping the device awake, for example:

  • Holding a WiFi lock only in your application, then responding to a network message pushed to the device in order to perform work at a specified time.
  • Use Firebase Cloud Messaging (FCM) to wake the device up to perform work at a specified time.