2011-05-20

Fixing GeekYouUp's Battery Widget

tl;dnr

GeekYouUp's Battery Widget for Android can be altered to reduce App Widget display update occurrences by as much as 40-65% (depending on device power use rate), without altering application functionality or otherwise hindering its ability to display the current battery charge level. Battery Widget's constantly running service, necessary for real-time monitoring of battery charge level changes, appears to use negligible resources and does not noticeably prematurely drain battery power.
Link To This ArticleQR Code Link To This Articlehttp://goo.gl/mscyj

Following are links to the code of the fixed version of Battery Widget, logs of the svn differences showing what was changed from the original code, and a fully-built Android APK, ready to be installed on any Android device. This APK was built with a different package name, a different application name, and a different digital signature than the Battery Widget application by GeekYouUp. It can be installed in parallel with the original -- it does not replace the original Battery Widget application during installation.

For Those With Interest In The Details...

GeekYouUp's Battery Widget is a popular Android application with over 4.2 million downloads. Unfortunately, it suffers from a few bugs, including unnecessarily (and ironically) using device battery power. Following is a brief description of GeekYouUp's Battery Widget functionality, problems, and proposed solutions. This information applies both to the current 1.6.7 version of Battery Widget available from the Android Market, and to the latest changes available in the application's source code repository (as of 2011.05.10), unless otherwise noted. Richard Hyndman, the developer behind GeekYouUp, was informed of these problems and of these proposed fixes in comments in the project's issue tracker and through Twitter.

Observed Application Functionality

GeekYouUp's Battery Widget is an Android application that displays the current battery charge level in a home screen App Widget, and provides quick access to toggle and adjust features that are major consumers of battery power, including display brightness, GPS, Wi-Fi, and Bluetooth settings. (The more a wireless service is used or the brighter the display is, the more battery power the device will consume.)

Battery Widget by GeekYouUpBattery Widget by GeekYouUp

Clicking on the App Widget instance launches an Activity (labeled "Settings" in the application code) that presents the user with five buttons: Battery, Display, GPS, Wifi, BT.

Battery Widget SettingsBattery Widget Settings

The "Battery" button launches Android's built-in "Battery use" screen, on devices that provide this feature. The "Battery use" screen lists the top consumers of battery power since the device was last turned on or charged. (Users can get to this same "Battery use" screen using the Settings application, and navigating to "About Phone" and then "Battery use".)

The "Display" button launches Android's built-in "Display settings" screen. (On some versions of Android, this is the "Sound & Display settings" or "Sound & display" screen.)

The "GPS" button launches the "Security & location settings" screen. (On some versions of Android, this is the "Security & Location" or "Location & security settings" screen.)

The "Wifi" button attempts to toggle the Wi-Fi service - turning it on if it were off, and turning it off if it were on - and displays a Toast message accordingly. Of course, if the device is not Wi-Fi enabled (a an emulator is typically not), then the Wi-Fi service is not toggled.

The "BT" button launches the "Wireless & network settings" screen. (On some versions of Android, this is the "Wireless controls" screen.)

Problems Observed During Installation And Use

While installing GeekYouUp's Battery Widget, and subsequently using it, the following problems were observed.

Permissions Inexplicably Required

It is surprising to see that a battery level indicator requires as many permissions as Battery Widget does, especially when other battery level indicator App Widgets don't require any permissions. Most suspect amongst the required permissions is "PREVENT DEVICE FROM SLEEPING". (Oh no! Is this battery level indicator going to stop my device from sleeping just to read and display the battery level?) Disappointingly, the application documentation on the Market provides no explanation for the needed permissions. (The answers must be in the code which we get to below.) Whatever the reasons are, they should be explained in the application documentation on the Android market, so users could make informed decisions before installation.

Initial Display State Is Incorrect

New Battery Widget instances incorrectly initially display 0% battery power, though the display does quickly update to show the current battery level.

Initial Battery Widget DisplayInitial Battery Widget Display

A possible fix for this minor issue would be to show a graphic that clearly indicates the App Widget is initializing, instead of initially displaying a 0% battery power graphic. For example, the graphic could be labeled "Loading...", or it could just not have an initial label.

Initial Battery Widget Display FixedInitial Battery Widget Display Fixed

Force Close On Android 1.5

Using the latest code from svn, on Android 1.5, clicking on the Battery Widget instance generated a Force Close error. (This problem was not observed on Android 1.6, 2.1 or 2.2. This problem was not observed using the APK from the Android Market.) Nothing in the user interface indicates what the problem is. The answer must be in the code (which we get to below).

Non-Functioning "Battery" Button On Android 1.5

If the device is running a version of Android that does not have the built-in "Battery use" screen, e.g., an Android 1.5 device, then clicking on the "Battery" button appears to do nothing (though it does throw an exception that is not displayed to the user). (The exception message is "ERROR/BatteryWidget(770): android.content.ActivityNotFoundException: No Activity found to handle Intent { action=android.intent.action.POWER_USAGE_SUMMARY }".)

A decent solution to this problem would probably be to remove the "Battery" button from the view on devices that don't support POWER_USAGE_SUMMARY intents.

Is It Draining Battery Power?

Since App Widgets that update more frequently will use more battery power than if they updated less frequently, we are of course interested in how often the Battery Widget polls for or receives changes, and in how often it updates the App Widget display, especially since it would seem reasonable that a battery monitoring application might make frequent updates, so that it can display accurate real-time information. Unfortunately, Battery Widget does not provide for a user-specified update interval. Also, it does not provide any documentation about update frequencies.

While users can see in the "Running services" screen (on Android devices that have this feature) that Battery Widget almost constantly has a service running, this does not necessarily mean that it's significantly using battery power. In fact, I can anecdotally report that on both my stock G1 and on my stock G2, I've noticed no difference in battery drain after installing this app. (I usually recharge my stock G1 every two days. During heavier use, I'll recharge after about 36 hours. I usually recharge my stock G2 at most twice per week. So far, my charging frequencies haven't changed after installing GeekYouUp's Battery Widget. It's been 4d 16h 14m since the last charge on the G2, with Battery Widget installed and running the entire time. (I do not currently pop-charge. I wait until the phone dies before connecting any cables. I may be changing this years-long habit based on recommended lithium-ion management practices described at http://en.wikipedia.org/wiki/Lithium-ion_battery, which is based on information at http://batteryuniversity.com/learn/article/charging_lithium_ion_batteries.)) Other users, however, have reported that this app "drained" their battery. But anecdotes and guesswork aren't enough. So, onward to the code...

In The Code

Permissions Inexplicably Required

The Android Manifest of Battery Widget declares that it uses the following permissions: ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, READ_PHONE_STATE, WAKE_LOCK, and WRITE_SETTINGS.

Use of ACCESS_WIFI_STATE and CHANGE_WIFI_STATE is clearly tied to the application feature that toggles Wi-Fi. Perhaps unexpectedly, so is WAKE_LOCK, and the application makes no other use of these permissions. (So, no, Battery Widget is not actually going prevent your device from sleeping.)

Use of ACCESS_FINE_LOCATION is similarly explained. The Battery Widget application attempts to access GPS state, to set the color of the GPS button label accordingly.

The declaration that READ_PHONE_STATE and WRITE_SETTINGS are used appears to be unnecessary. With these declarations removed, the application still works as expected (with no security permissions errors or other changes observed).

Force Close On Android 1.5

After receiving the Force Close screen when clicking on the App Widget instance running on the Android 1.5 emulator, in LogCat the reported error message was:
WARN/dalvikvm(777): threadid=3: thread exiting with uncaught exception (group=0x4000fe70)
ERROR/AndroidRuntime(777): Uncaught handler: thread main exiting due to uncaught exception
ERROR/AndroidRuntime(777): java.lang.VerifyError: com.geekyouup.android.widgets.battery.TranslucentBlurActivity
ERROR/AndroidRuntime(777): at java.lang.Class.newInstanceImpl(Native Method)
ERROR/AndroidRuntime(777): at java.lang.Class.newInstance(Class.java:1472)
ERROR/AndroidRuntime(777): at android.app.Instrumentation.newActivity(Instrumentation.java:1097)
While this didn't point to a line of code or refer to a common error message, it at least mentioned TranslucentBlurActivity, where the problem was easy to find. TranslucentBlurActivity uses an Android API component introduced with level 4. Since Andriod 1.5 uses API level 3, this is a problem. The offending code is the use of android.os.Build.VERSION.SDK_INT.

The fix is to change the use of VERSION.SDK_INT back to use VERSION.SDK, as previous versions of the code base did. With this fix in place, Battery Widget deploys and runs fine on Android 1.5 (with the exception of the "Battery" button, of course.)

Is It Draining Battery Power?

GeekYouUp's Battery Widget does not directly update the App Widget display from onUpdate(Context, AppWidgetManager, int[]) in its AppWidgetProvider. Instead, like many App Widgets, onUpdate starts an update service to perform updates in a background process. Battery Widget does attempt to configure the AppWidgetProviderInfo to update every five minutes, but the App Widget framework disregards this value, and instead sends update requests every thirty minutes. On my stock G1 and on my stock G2, every call to start the update service does cause the service's onStart(Intent, int) to run, without consideration for whether the service were already running. onStart then updates the display of any Battery Widget App Widget instances.

In addition to these scheduled updates, the update service also registers a BroadcastReceiver to respond to all ACTION_BATTERY_CHANGED broadcasts. When this BroadcastReceiver receives such broadcasts, similar to this application's AppWidgetProvider, it starts the update service, which then updates the display of any Battery Widget instances, as previously described.

After performing any display updates, instead of stopping itself, as one might expect a well-behaved App Widget update service would do, Battery Widget's update service keeps running, though it does not perform any further processing. It does so in order to keep the ACTION_BATTERY_CHANGED broadcast receiver alive, because ACTION_BATTERY_CHANGED notifications do not wake receivers. (That ACTION_BATTERY_CHANGED receivers only receive notifications when they are running is unexpectedly documented in Intent.ACTION_POWER_DISCONNECTED, but not mentioned in the ACTION_BATTERY_CHANGED section.) So, to respond to battery changes, an application must either be running, in which case it can immediately respond to ACTION_BATTERY_CHANGED notifications, or it must explicitly check for battery changes according to a schedule, or in response to some other event.

This current processing does leave room for some optimizations to reduce updates, without adversely affecting Battery Widget's ability to display up-to-date battery information. Unnecessary processing can be eliminated by not updating the display if the display already correctly reflects the current battery level and charging state. With this change, display updates were reduced by about 40% on my G1 (from an average 5.9 updates per hour, to 3.4), and by a little more than 65% on my G2 (from an average 2.8 updates per hour, to 0.9), during normal use.

It's worth noting that my "normal" use significantly differs from many folks' use, as I don't often play videos, music or graphics intensive games. Also, I leave wireless features off when not in use. This undoubtedly means that my devices use power at relatively slower rates, resulting in slower battery charge level change rates, resulting in more significant Battery Widget update reductions than what greater power users would experience.

More Aggressive Power Saving Changes

While it might take a fancy power meter to determine what (if any) affects on power draw these changes make, Battery Widget should be a good App Widget citizen, by providing users the ability to configure how frequently updates occur, and the ability to specify whether the update service should remain running. (This post is already long, so I'll not further rehash common arguments on this topic.)

To this end, a simple implementation would be to provide another button on the current "Settings" Activity screen, that when clicked presents users with Battery Widget configuration options for specifying the duration between scheduled updates, and whether to run a background service to automatically respond to ACTION_BATTERY_CHANGED broadcasts. This new options screen would also be a good place to provide users with information about how these settings affect application behavior and possibly affect power consumption.

Additional changes that might marginally improve power consumption further include:
  • When the screen is off, don't process any updates, and don't leave any services running. While this seems to be a good idea on the surface, it might not significantly reduce power consumption, as the Android system appears to put dormant processes to sleep when the screen is off and some inactivity threshold is met.
  • When the screen is turned on, if the update service was turned off when the screen was turned off, update the widget display, if appropriate, and start the ACTION_BATTERY_CHANGED receiver.
  • Provide a quick link to toggle NFC.

One More Small Change: Consider Device Scale

The BatteryManager documentation describes that the battery level is not necessarily a value from 0 to 100, but instead is a value "from 0 to EXTRA_SCALE". GeekYouUp's Battery Widget code currently assumes that the max level value is 100. The fix for this is to apply the calculation 100 * currentLevel / scale to get the correct level percentage for display on the App Widget.

...And Finally The Last Change: Workaround For Android 1.5 Delete App Widget Bug

As discussed in the Android Developers mailing list, Android 1.5 has a bug where updates for deleted App Widgets continue to process. The workaround "fix" for this bug is to check for ACTION_APPWIDGET_DELETED Intents in onReceive of the AppWidgetProvider, and ensure that onDeleted is called accordingly.

References And Resources:


FINI

3 comments:

  1. Wow! I'll have to spend some time reading this thoroughly and get back to you, I just wanted to say hi and thank you. We'll get it sorted in the live version.

    ReplyDelete
  2. Thanks very much for your effort in putting together this post. I've learned a lot since I'm really new to Android programming. :)

    ReplyDelete
  3. Hey, great article and code, it's been very helpful! Though I have one problem - the battery state doesn't update when I'm not at my Homescreen. I've checked that it keeps receiving the broadcast and it reads the battery level correctly, but it doesn't update any of the remoteViews :/ It just keeps showing the level from the last time I was at the Homescreen.
    Do you think there's something possible to do here, or is it some kind of Android limitation that it doesn't allow updating remoteViews when Homescreen is not visible?

    Thanks in advance!

    ReplyDelete