2

I need to find out when the last user interaction was, regardless of which application is on top. I don't care about where or what the event was I just need to know when it was. Or alternatively, as it happens, I receive an event.

I've tried multiple things:

  1. Create service with a window and added a touch listener. This ate the touch event and didn't pass it down
  2. Looked for a shell command. getevent works (new line comes in every time a touch is received) however you need root and so it is not an appropriate solution for me.
  3. Looked for "time until lock" but came up with nothing.

Also note: There is no security concern with this as I don't need any identifying information such as touch location. Just a type stamp (or live event).

I'm open to using reflection to figure it out as well.


@user2558882 has a very good solution. As of now, that's the best approach I've come across.

While that's great, it still requires the user to manually enable our application in the accessibility controls. We have customers with thousands of devices and we have a way to automatically update and change settings. We try and keep manual configuration to a minimum, but some things still require user input such as enabling Device Admin mode. So this solution is acceptable however I'm still open to a way that doesn't require any user input to enable.


I ended up implementing @user2558882's idea to use an accessibility service. Though other ideas are welcome.

Randy
  • 4,351
  • 2
  • 25
  • 46

2 Answers2

3

This is just an idea, and may not be fully transferable.

Here's what an AccessibilityService can do:

An accessibility service runs in the background and receives callbacks by the system when AccessibilityEvents are fired. Such events denote some state transition in the user interface, for example, the focus has changed, a button has been clicked, etc.

You will be informed of the event inside onAccessibilityEvent(AccessibilityEvent):

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

    // Some event

    timeSinceLastInteraction = System.currentTimeMillis();

}

You could periodically log the updates:

Log.i("LOG_TIME_ELAPSED", "Last user interaction was " + 
             ((System.currentTimeMillis() - timeSinceLastInteraction) / 1000) + 
                      " seconds ago.");

There are two ways in which you can configure your AccessibilityService:

  1. In code, inside onServiceConnected(). (API 4 onwards)

  2. In xml, using the meta-data tag in your service. (API 14 onwards)

In your application's case, you could probably set AccessibilityServiceInfo.eventTypes to:

accessibilitySeviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;

But, TYPES_ALL_MASK will include notifications such as: AccessibilityEvent.TYPE_ANNOUNCEMENT, AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED etc. which I am guessing you do not care to intercept. So, you'll need to choose a subset of AccessibilityEvent.TYPE_X.

Another thing you should be careful about is the notification timeout:

The timeout after the most recent event of a given type before an AccessibilityService is notified.

The event notification timeout is useful to avoid propagating events to the client too frequently since this is accomplished via an expensive interprocess call. One can think of the timeout as a criteria to determine when event generation has settled down.

So, be generous with the timeout value.

You'll find this page very helpful in case you decide to go with the AccessibilityService option: Developing an Accessibility Service.

From your comments to Chloe's answer, it seems that the device is under your control: meaning, to some extent, you don't have to rely on the user for enabling the service:

The lifecycle of an accessibility service is managed exclusively by the system and follows the established service life cycle. Additionally, starting or stopping an accessibility service is triggered exclusively by an explicit user action through enabling or disabling it in the device settings.

You can enable the AccessibilityService at time of deployment, and perhaps restrict access to Settings menu using an app like AppLock.

Another option is to check whether your AccessibilityService is enabled from time to time:

AccessibilityManager am = (AccessibilityManager)
                               getSystemService(ACCESSIBILITY_SERVICE);

List<AccessibilityServiceInfo> listOfServices = 
                             am.getEnabledAccessibilityServiceList(
                               AccessibilityServiceInfo.FEEDBACK_ALL_MASK);

for (AccessibilityServiceInfo asi : listOfServices) {

    // Check if your AccessibilityService is running

}

In case the AccessibilityService has been disabled by a inquisitive/notorious user, you can lock the device by displaying a fullscreen view with text: Device has been locked. Contact a Sales Rep to unlock this device.

Vikram
  • 51,313
  • 11
  • 93
  • 122
  • Wow. Ton of information, thank you very much. I will look into this sometime tomorrow and let you know my results. – Randy Sep 26 '13 at 03:53
  • 1
    I updated my question. Your solution works great but would like a way which requires no user input to enable. If no such thing, I'll definitely be using the `AccessibilityService`. – Randy Sep 26 '13 at 04:38
  • @Randy Good to know. Enabling the service on a per device basis does sound like a problem. The best you can do is: take the user to the Accessibility Settings page on first run. More on this here: [Link](http://stackoverflow.com/a/15974165). Hopefully, someone will suggest a better solution. – Vikram Sep 26 '13 at 04:56
  • @Randy Hey Randy, just wondering if this solution was indeed feasible &| implementable. – Vikram Oct 10 '13 at 05:50
  • It's the solution we ended up implementing, however, it's not perfect. If a user is using Chrome and scrolls the web page it doesn't register as an accessibility event and so we don't count it as user interaction. – Randy Oct 10 '13 at 12:46
1

I believe you have to return false to indicate that your transparent service window did not consume the touch event. That way the event loop will pass it down the stack to the lower windows.

https://developer.android.com/reference/android/view/View.OnTouchListener.html

Another possibility, is to add a service which listens for motion/accelerometer events.

https://developer.android.com/guide/topics/sensors/sensors_motion.html

Another possibility is to listen for ACTION_USER_PRESENT

https://developer.android.com/reference/android/content/Intent.html#ACTION_USER_PRESENT

or ACTION_SCREEN_ON

https://developer.android.com/reference/android/content/Intent.html#ACTION_SCREEN_ON

Chloe
  • 25,162
  • 40
  • 190
  • 357
  • The OnTouchListener I know doesn't work. Once you register a listener the window captures the click and it doesn't care which view actually consumes it. I really like the motion/accelerometer idea, however, it won't work for my application. I'm working on a kiosk application where the device might be sitting in an enclosure, never moving. – Randy Sep 19 '13 at 05:26
  • Also note, if simply returning false worked, it would present a security concern. You receive the touch type and location in the onTouch event. And if you could put a transparent view on top of everything you would be able to tell exactly where touches were (even on the keyboard). – Randy Sep 19 '13 at 05:32
  • @Randy Ok added more ideas. – Chloe Sep 19 '13 at 14:51
  • Neither solution will do, sadly. – Randy Sep 25 '13 at 20:53