34

I'm going to create widget which needs to update its content every minute (it shows time-related data).

However, there is no need to update widget if it is currently invisible, which means:

  • screen is turned off
  • another app is running
  • widget is placed on another (invisible) home screen tab

What is the best way to update only visible widget every minute - without waking up device nor doing unnecessary computations? After widget becomes visible, small lag before update is acceptable.

awy
  • 5,271
  • 5
  • 23
  • 28
tomash
  • 12,742
  • 15
  • 64
  • 81

4 Answers4

20

To keep from updating when the screen is off, use the AlarmManager to schedule a recurring alarm that doesn't wakeup the phone.

The other two bullet points you have in your question aren't possible. There is no way to detect if your widget is on a home screen that isn't currently visible, and there is no way to determine if an app is running that is hiding the home screen. I have filed a ticket on http://b.android.com requesting this functionality be added to Android. If you feel like starring it, it will help it gain priority: http://code.google.com/p/android/issues/detail?id=5529&q=reporter:mark.r.baird&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

Chris Stillwell
  • 10,266
  • 10
  • 67
  • 77
Mark B
  • 183,023
  • 24
  • 297
  • 295
  • I starred, at least this I can do, you answered so many questions of mine, and probably will do so in the future. Thanks. – Pentium10 Mar 10 '10 at 14:12
  • Starred, too bad it won't be available in millions of hadsets that are currently in use... – tomash Mar 10 '10 at 14:18
  • 1
    It is possible to determine which app is currently running: ActivityManager mAM = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); List tasks=mAM.getRunningTasks(1); – Niels Jun 16 '12 at 14:42
  • Why would you need to use an alarm. You get that for free from the widget metadata. You can change the updatePeriodMillis attribute to change the frequency at which onUpdate is called for the widget. See the doc: https://developer.android.com/guide/topics/appwidgets/index.html#MetaData – catalyst294 Mar 30 '14 at 19:44
  • @rmmcnulty9 Please read the documentation you linked to: "If the device is asleep when it is time for an update (as defined by updatePeriodMillis), then the device will wake up in order to perform the update. If you don't update more than once per hour, this probably won't cause significant problems for the battery life. If, however, you need to update more frequently and/or you do not need to update while the device is asleep, then you can instead perform updates based on an alarm that will not wake the device." – Mark B Mar 31 '14 at 15:58
  • The link of code.google.com has been marked recently obsolete, does this mean that this can already be done today? – htafoya Apr 22 '15 at 21:49
  • 1
    @htafoya no that probably means that some guy at Google was tasked with spring cleaning in the issue tracker and closed this task along with 535 others on march the 15th: https://code.google.com/p/android/issues/list?can=1&q=status%3AObsolete+closed-after%3A2015%2F03%2F15+closed-before%3A2015%2F03%2F16&sort=stars&colspec=ID+Type+Status+Owner+Summary+Stars&cells=tiles – Nilzor May 06 '15 at 09:52
  • 2
    @catalyst294 The problem with this approach is updatePeriodMillis minimum value is 30 minutes and some widgets need smaller intervals for updates. – Ahmed Korany Jan 22 '19 at 12:11
12
  1. You can ask the PowerManager about isScreenOn()
  2. You can register for Intent ACTION_SCREEN_OFF/ACTION_SCREEN_ON and turn you timer on/off accordingly.

Allthough the answer above concerning the AlarmManager is correct it may not be sufficient since I observed many phones also delivering alarms even if they are not of type *_WAKEUP. This may happen if there are other applications installed that are waking up the device. And if it is once awake, it delivers all pending alarms.

jom
  • 121
  • 1
  • 2
  • This is a very important point. The alarm may fire even if the screen is asleep, as explained in [this thread](https://groups.google.com/forum/?fromgroups=#!topic/android-developers/Airlo3g5luw). – lseidman Feb 22 '13 at 00:22
  • I also noticed that with developer mode on (and logging content to the logcat), that the device is staying awake. To test what it would do without dev mode, I added a counter for all the updates to display on the widget and noticed that with developer mode off it will go to sleep and will not update the widget with the AlarmManager.RTC events. I can't find any documentation to see that the developer mode is keeping it awake, but it does make sense to me. – Renso Lohuis Oct 27 '14 at 12:03
6

I found this under a project named 24clock in goole code. It try Not update the widget while user is away from home:

    ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

    List<RunningTaskInfo> runningTasks = am.getRunningTasks(2);
    for (RunningTaskInfo t : runningTasks) {
        if (t != null && t.numRunning > 0) {
            ComponentName cn = t.baseActivity;
            if (cn == null) continue;

            String clz = cn.getClassName();
            String pkg = cn.getPackageName();

            // TODO make this configurable
            if (pkg != null && pkg.startsWith("com.android.launcher")) {
                return true;
            }

            return false;
        }
    }

It might be an answer for you requirement #2. Although it might not work under other 3rd party launchers.

== Update ==

A few months later, I suddenly have an idea in my mind about how to detect whether home screen is showing. I put it in my blog, and I ran test on my Desire and it works fine. The method is to query the Android for all installed packages, which one of it contains the "Launcher" ability, and then check whether it is on the top of running stack. If anyone have tested please let me know the result as well, since I have no access to other Android devices.

xandy
  • 27,357
  • 8
  • 59
  • 64
1

The accepted answer from mbaird hits the nail on the head. The proposed onVisivilityChange() method, if implemented, should cover all the cases above.

In the meantime this is still a real problem for some types of widget. jom introduced the possibility of registering to receive ACTION_SCREEN_OFF/ACTION_SCREEN_ON intents. This is useful because relying on a non-wakeup recurring alarm is not sufficient as other services make cause wakeups. It is difficult because such actions cannot be subscribed to via AndroidManifest.xml and an AppWidgetProvider is not permitted to call context.registerReceiver(). These issues are discussed in several other StackOverflow questions including Listening for ACTION_SCREEN_OFF, android.intent.action.SCREEN_ON doesn't work as a receiver intent filter and Android - how to receive broadcast intents ACTION_SCREEN_ON/OFF?.

I have succeeded in subscribing to ACTION_SCREEN_OFF/ACTION_SCREEN_ON intents in a widget by creating a subsidiary BroascastReceiver instance and using context.getApplicationContext().registerReceiver() to register it. This could well be cheating and, at least in some subsequent Android release, may fail at the registration stage or perhaps the events will simply not be delivered. I have coded to handle these cases but for now it works. Unfortunately, of course, this will fail if and when the application is ever killed.

Another possibility is to use an isHomeScreenShowing() sort of method, as described here, referenced by xandy's answer. The ideas there could probably be optimized by caching the generated list of installed CATEGORY_HOME applications and listening to ACTION_PACKAGE_ADDED/CHANGED/REMOVED broadcasts to update it.

My strategy is:

  • Try to avoid being called (via a repeating alarm) when not visible.
  • When called, check screen state and other visibility indicators before doing anything expensive.
  • Only then invoke an IntentService to handle relatively expensive work, which includes a persistent network connection. This is necessary to monitor the state of a remote service in near real time.
Community
  • 1
  • 1
awy
  • 5,271
  • 5
  • 23
  • 28