在安卓系统中检测用户活动

6
我想要检测用户上次与屏幕交互的时间。我不感兴趣进行任何恶意软件/间谍软件相关操作,只需要计算自用户上次点击屏幕以来经过了多长时间。
目标是实现类似于按键锁屏的功能。
我已经进行了一些研究,并遵循了网站上的一些Q&A(例如Android最佳检测和处理用户不活动的方法Android:检测用户的一般使用情况等),但在检测Android中的用户交互方面,我没有找到我所需的内容。
先行致谢。

虽然你可以检测到自己的活动,但我不知道除了在已经root的设备上,是否有一种方法可以检测整个系统的活动。 - CommonsWare
我正在使用一种解决方案来检测我的活动之外的每一个触摸。你能帮我检查一下吗? - Junior Buckeridge
4个回答

12

经过更多的研究和测试后,我已经找到了解决方案。

我做的是创建一个后台服务,在其中获取WindowManager的实例并添加一个零大小视图,该视图会在用户触摸其外部时得到通知...但由于视图大小为零,因此它每次都可能被调用。 我们可以在https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html中查看相关内容。

InactivityService.java类会检测用户每次点击屏幕并将其写入首选项中,以便在应用程序的其他组件中显示或使用。 值得注意的是,我们可以广播时间,或将其传递给我们的应用程序对象或任何其他适合或需要的解决方案。

/**
 * 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;
    }
}

我们应该在清单文件中添加以下权限:

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

同时需要提到的是,结合使用 ActivityManager 获取正在运行的任务,这是一种非常有效的方式来确定用户是否正在与手机交互。


2
如果您请求SYSTEM_ALERT_WINDOW权限,那些注重安全的用户将不会使用您的应用程序。我从不安装请求此权限的应用程序,并建议其他人也不要这样做。在这种情况下,似乎没有任何重大的隐私问题,因为传递给您的MotionEvent是被切除了的(根据在4.4模拟器上进行的轻微测试)。我不知道您是否会在使用此类技术的其他应用程序中遇到问题(例如,它们代替您获取事件),但在实际应用中应该不常见。 - CommonsWare
我知道这可能会“触发”许多用户的警报,但是正如您所看到的,根据Android文档,此代码没有安全问题。无论如何,这是我找到的实现目标的唯一方法。敬礼。 - Junior Buckeridge
谢谢,这个有效。然而,它只能检测到触摸开始的时候,无法检测到触摸结束的时候。 - Sam
哦,当用户触摸底部栏时,没有触摸事件被观察到。 - Yeung
请注意,这在Android O中有所改变,并且将始终在通知栏中显示条目。此外,在Android 6及以上版本中,您需要查询权限。 - Kenny
@JuniorBuckeridge 你好,我们在一个应用程序中使用了相同的解决方案,但似乎在搭载Android 10的Google Pixel 3 XL上无法正常工作,你有什么想法吗?在该设备上,我们无法获取触摸事件。 - Renjith K N

2
您可以使用一个辅助功能服务,它可以让您监听点击、滚动和其他交互事件。但是有一个限制,除非它们触发操作,否则它不会报告原始的触摸事件。

1
AccessibilityService只能由用户在设备设置中明确打开,才会被触发。因此无法通过编程的方式打开它。 - DH28

1
我成功改进了Junior Buckeridge的解决方案,使得不再需要SYSTEM_ALERT_WINDOW权限。思路是相同的:我们添加一个视图来接收所有onpress事件。但我的解决方案只是在活动的onCreate中添加这个视图,并在创建LayoutParams时使用WindowManager.LayoutParams.TYPE_APPLICATION而不是WindowManager.LayoutParams.TYPE_SYSTEM_ALERT。
    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);

0

我正在使用@Junior Buckeridge的解决方案,但实际上我遇到了一个崩溃,因为我们不允许在应用程序窗口上绘制。(Bad token exception)

真正帮助我的是:

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
);

在你的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);
}

并且添加:

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

来源: Android:无法添加窗口。此窗口类型没有权限 (Anam Ansari的回答)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接