6

I would like to detect when was the last time the user interacted with the screen. I'm not interested in doing any malware/spyware stuff, just need to calculate how much time has elapsed since the last time the user tapped on the screen.

The goal is to achieve a functionality similar to that of a keyguard.

I've been doing some research and following some to Q&A on the site (such as Android Best Way to Detect and Handle User INACTIVITY, Android: Detect General Use by User among others) but when it comes to detect user interaction in android I haven't found what I need.

Thanks in advance.

Community
  • 1
  • 1
Junior Buckeridge
  • 2,075
  • 2
  • 21
  • 27
  • While you can detect that for your own activities, I am not aware of a means by which you can detect that for the system as a whole, except perhaps on rooted devices. – CommonsWare Jan 21 '14 at 19:45
  • I'm using a solution to detect every touch outside my own activity. Could you give it a check? – Junior Buckeridge Jan 22 '14 at 00:22

4 Answers4

12

After some more research and testing I've managed to rise with a solution.

What I did was to create a background service in which I get an instance of the WindowManager and add a zero sized view that get's notified of user touches outside of it... but as the view has zero size it can get called every time. For that we may check https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html

The InactivityService.java class detects every time the user taps the screen and write it to the preferences so it can be displayed or employed later in other components of the app. It's valid to notice that we can broadcast the time, or pass it to our app object or whatever other solution suits or needs.

/**
 * Detects user taps on screen (user interaction).
 * 
 * @author Junior Buckeridge A.
 *
 */
public class InactivityService extends Service {

    protected static final String LOG_TAG = InactivityService.class.getSimpleName();
    private boolean isAdded = false;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if(!isAdded){
            WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                    0, 0, 0, 0,
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                    PixelFormat.TRANSLUCENT);
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
            View view = new View(this);
            view.setOnTouchListener(new OnTouchListener() {

                @SuppressLint("DefaultLocale")
                @Override
                public boolean onTouch(View v, MotionEvent event) {

                    GregorianCalendar calendar = new GregorianCalendar();
                    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(InactivityService.this);
                    String log = prefs.getString(LOG_KEY, "");
                    log = String.format("User interaction detected at: %02d:%02d:%02d \n%s", 
                                calendar.get(Calendar.HOUR_OF_DAY),
                                calendar.get(Calendar.MINUTE),
                                calendar.get(Calendar.SECOND),
                                log);
                    prefs.edit().putString(LOG_KEY, log).commit();

                    return false;
                }
            });
            wm.addView(view, params);
        }

        return START_STICKY;
    }
}

We should add the following permission to the manifest:

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

Also valid to mention that this method, combined with getting the running tasks with ActivityManager is a very effective way to determine if the user is interacting with the handset.

Junior Buckeridge
  • 2,075
  • 2
  • 21
  • 27
  • 2
    You'll lose security-conscious users with the `SYSTEM_ALERT_WINDOW` permission. I never install apps that request it and advise others to do the same. In this particular case, there does not seem to be any significant privacy issue, as the `MotionEvent` delivered to you is lobotomized (based on light testing on a 4.4 emulator). I don't know whether you will encounter problems with other apps that also use this same sort of technique (e.g., they get events instead of you), but that should not be all that commonly encountered in the field. – CommonsWare Jan 22 '14 at 00:53
  • I know it can "trigger the alarm" in many users, but as you can see there is no security issues with this code (based on the android docs). Anyways, its the only way I've found to achieve the goal. Regards. – Junior Buckeridge Jan 22 '14 at 01:10
  • Thanks; this works. However, it only detects when a touch starts. It doesn't detect when it finishes. – Sam Nov 21 '14 at 22:34
  • Oh, when user touch the bottom bar, there is no touch event observed. – Yeung Dec 18 '14 at 04:28
  • Note that this changes in Android O and will display a entry in the notification shade at all times. Additionally, you need to query for permissions in Android 6 and above. – Kenny Aug 29 '17 at 17:00
  • @JuniorBuckeridge Hi, we are using the same solution in one of our application, but it seems this is not working on Google Pixel 3 XL with Android 10, do you have any idea about it? In that device, we do not get touch events – Renjith K N Nov 26 '19 at 12:30
2

You could use an AccessibilityService, which would allow you to listen for click, scroll, and other interaction events. One limitation is that it doesn't report raw touch events unless they trigger actions.

Rupert Rawnsley
  • 2,622
  • 1
  • 29
  • 40
  • 1
    AccessibilityService is triggered exclusively by the user explicitly turning the service on in device settings. So no way to turn it on programmatically. – DH28 Jun 23 '16 at 12:52
1

I managed to improve the solution of Junior Buckeridge such that the SYSTEM_ALERT_WINDOW permission is not required anymore. The idea is the same: we add a view that receives all onpress events. But my solution just adds this view in the onCreate of the activity and uses the WindowManager.LayoutParams.TYPE_APPLICATION instead of WindowManager.LayoutParams.TYPE_SYSTEM_ALERT when creating the LayoutParams.

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            0, 0, 0, 0,
            WindowManager.LayoutParams.TYPE_APPLICATION,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
            PixelFormat.TRANSLUCENT);
    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    View view = new View(this);
    view.setOnTouchListener(new View.OnTouchListener() {

        @SuppressLint("DefaultLocale")
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("Test", "Hello World!");

            return false;
        }
    });
    wm.addView(view, params);
Stef
  • 621
  • 4
  • 3
0

So I am using @Junior Buckeridge solution, but actually I got a crash, since we are not allowed to draw over the application window. (Bad token exception)

What really helped me:

windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
//here is all the science of params
final LayoutParams myParams = new LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        LayoutParams.TYPE_SYSTEM_ERROR,
        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
        PixelFormat.TRANSLUCENT
);

in your Activity:

 if(Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(Activity.this)) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, 1234);


   }
}else{
    Intent intent = new Intent(Activity.this, Service.class);
    startService(intent);
}

and also add the:

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

source: Android: Unable to add window. Permission denied for this window type (Anam Ansari's answer)

narancs
  • 5,234
  • 4
  • 41
  • 60