The Problem With Android’s ACTION_USER_PRESENT Intent

First, the background.  I bought the Nexus S when it first came out.  I had come to rely on the notification LED on my previous phone, and this phone’s lack of one was quite annoying.  So, I set out to fix my problem by writing NotificationPlus (source here), an Android app that provides recurring notifications via a ringtone and/or the vibrator.

There are a couple of things such an app has to be able to detect.  First, it has to detect incoming events, such as SMS, missed calls, voicemail, email, etc.  I’ll leave the problems with the Android API in that space for another post.  In this post, we’ll focus on the second thing the app has to accomplish, and that is to tell whether or not the user is actively using the phone.  Sounds simple, right?  In my first take, I just checked to see if the screen was turned on.  Surely, if the screen is unblanked, that means someone is looking at the phone, right?  Wrong!  Let’s explore the variety of ways the screen becomes unblanked, shall we?

  1. the user pushed the power button, either intentionally or not (think butt dialing)
  2. an application decided to unblank the screen, such as:
    1. the phone app, which unblanks the screen for an incoming call
    2. messaging apps, such as GoSMS, wh ich unblank the screen when a message arrives
    3. any other app may do this

As you can see, you can’t infer from the ACTION_SCREEN_ON intent that it is triggered by a user.  Thus, it cannot be relied upon for determining when to disable the repeating notification.  Many of the complaints in the Android Market comments of the form, “did not work, uninstalled,” I am sure boil down to this problem.

My next crack at fixing the problem was to utilize the very promising ACTION_USER_PRESENT intent.  Surely this would be the ticket!  Nope.  Not even close.  ACTION_USER_PRESENT is broadcast only if there is a lock screen enabled.  So, if the lock screen preference is set to none, this intent is never broadcast.  That sounds minor, as I doubt many people run this way.  However, there is another, related problem.  How do you determine when the user is no longer present?  You would think that this sort of intent would have a complement, right?  Like ACTION_USER_ABSENT (;-)) or maybe just ACTION_SCREEN_LOCKED.  There is no such intent.  Why does it matter?  Well, when a lock screen, such as pattern lock or pin, is configured, the user has the option to delay locking after the screen has blanked.  So, let’s say you’re reading a web page, and the screen blanks before you finish.  You hit the power button, and the phone turns back on without requiring the unlock code (and hence without firing the ACTION_USER_PRESENT intent).  If you were relying on a screen blank to tell when the user is no longer present, then you are screwed.  Now, this would be ok, so long as there was a way to query the system preferences to tell what the lock delay was set to, but there isn’t.

So, that’s the end of my ranting.  This basically means that, without asking the user a bunch of questions about their configuration, there is no way to have a one size fits all solution to this problem.  I can write a ton of heuristics, but they are bound to fail for some corner case or another.  All of this could be avoided if the android OS just provided a recurring notification option in the settings.  Or, you know, they could fix the API.

Advertisements

3 thoughts on “The Problem With Android’s ACTION_USER_PRESENT Intent

  1. Use

    private String getEventType(AccessibilityEvent event) {
    switch (event.getEventType()) {
    case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
    return “TYPE_NOTIFICATION_STATE_CHANGED”;
    case AccessibilityEvent.TYPE_VIEW_CLICKED:
    return “TYPE_VIEW_CLICKED”;
    case AccessibilityEvent.TYPE_VIEW_FOCUSED:
    return “TYPE_VIEW_FOCUSED”;
    case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
    return “TYPE_VIEW_LONG_CLICKED”;
    case AccessibilityEvent.TYPE_VIEW_SELECTED:
    return “TYPE_VIEW_SELECTED”;
    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
    return “TYPE_WINDOW_STATE_CHANGED”;
    case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
    return “TYPE_VIEW_TEXT_CHANGED”;

    }
    return “default”;
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s