应用锁(AppLocker)原理及代码实现

最近工作上要用到应用锁(AppLocker),早上装了个试玩了一下,下午花了2个小时捣鼓出来一个demo (基于android 6.0),简单分享一下。

免责声明:这篇文章纯粹是个人YY,未参考或反编译任何商业app 

所谓应用锁,说白了就是在监测到目标app启动时额外起一个锁屏界面把它盖住。这种方式的缺点是显而易见的,无法100%保证盖住,有时候会先闪出一个app启动界面,然后才被盖住。最彻底的方式当然是在framework里拦,不过这种方式需要跟平台厂商合作,不在今天的讨论范围之内。

先看一下效果,有两种盖法,一种是起一个新的activity,还有一种是起一个悬浮窗。我没有比较过这两种方式的优劣,先采用了第一种。悬浮窗之前封装了一个类,后面有时间再写文章分享出来。

 

下面讨论实现细节:

在早期的android版本中,大家都是通过getRunningTasks()来获取前台进程的,但是出于安全考虑这个API已经被google阉割了,要用这个API必须要申请REAL_GET_TASKS权限,但是这个权限又是signature or system的,所以基本上是然并卵。。。

android 6.0上,纯app层面,获取前台进程的唯一合法途径可能就是通过UsageStat的方式了,它原本是用来统计app使用情况的,但是可以根据时间戳获取最近使用的app来达到目的。需要在AndroidManifest.xml里添加PACKAGE_USAGE_STATS权限,同时要在Settings的“安全--> 有权查看使用情况的应用”里给你的应用打开这个权限。代码如下:

private String getForegroundApp() {
UsageStatsManager usageStatsManager = (UsageStatsManager) getApplicationContext()
                                    .getSystemService(Context.USAGE_STATS_SERVICE);
    long ts = System.currentTimeMillis();
    List queryUsageStats 
        = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, 0, ts);
    if (queryUsageStats == null || queryUsageStats.isEmpty()) {
        return null;
    }
 
    UsageStats recentStats = null;
    for (UsageStats usageStats : queryUsageStats) {
        if(recentStats == null ||
          recentStats.getLastTimeUsed() < usageStats.getLastTimeUsed()) {
              recentStats = usageStats;
        }
    }
 
    if (DEBUG) Log.d(TAG, "getForegroundApp: " + recentStats.getPackageName());
    return recentStats.getPackageName();
}

queryUsageStats()方法里的两个参数0ts表示统计从开机到当前时间所有app的使用情况,然后就是比较时间戳找到最近一次使用的app,返回它的package名称。

既然要持续监控前台进程,自然需要一个service,我们把它命名为AppLockerService。为避免阻塞主线程,这里创建一个新的HandlerThread,然后每隔300ms通过Handler发一个消息去检查前台进程,如果在监控列表中就启动锁屏界面(AppLockerActivity),通过在intent中添加一个APP_NAME字段表示被锁apppackage名称。

另外有个需求是锁屏以后需要重新验证一次手势密码,因此需要通过KeyguardManager判断一下是否在锁屏,是的话就清楚掉解锁状态。代码如下:

private final int MONITOR_INTERVAL = 300;
HandlerThread mMonitorThread = new HandlerThread("AppLockerMonitorThread");
mMonitorThread.start();
Handler mHandler = new Handler(mMonitorThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_CHECK_FG_APP:
                if (mKeyguardManager.inKeyguardRestrictedInputMode()) {
                    resetUnlockStatus();
                    break;
                }
 
                String foregroundApp = getForegroundApp();
                 if (shouldLock(foregroundApp)) {
                    Intent intent = new Intent(
                        AppLockerService.this, AppLockerActivity.class);
                    intent.putExtra("APP_NAME", foregroundApp);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                }
                break;
        }
 
        mHandler.sendEmptyMessageDelayed(MSG_CHECK_FG_APP, MONITOR_INTERVAL);
    }
};
mHandler.sendEmptyMessage(MSG_CHECK_FG_APP);

这个锁屏的界面很简单,上面是一个被锁app的图标,下面是一个九宫格的锁。本来想自己写这个锁的,网上一搜有位仁兄已经封装好了,而且注释巨详细,拿来用就行了,在此向这位仁兄致敬!我们不生产代码,我们只是代码的搬运工,哈哈~~

每次手势完成以后会调用一个onGestureEvent()的回调函数,如果密码匹配的话就通知AppLockerService更新解锁状态并退出:

public void onGestureEvent(boolean matched) {
    if (matched) {
        Intent intent = new Intent(AppLockerActivity.this, AppLockerService.class);
        intent.putExtra("UNLOCK_APP", mAppName);
        startService(intent);
        finish();
    }
}

最后要写一下主界面,就是列举一下所有非系统的 app ,以及他们被锁的状态,放到一个 ListView 里显示。用户点击以后会更新图标,把设置保存到 SharedPreferences 里,还有通知 AppLockerService 更新 app 列表。

private void initAppList() {
    List packages = getPackageManager().getInstalledPackages(0);
 
    for (int i = 0; i < packages.size(); i++) {
        PackageInfo packageInfo = packages.get(i);
        if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) ==  0) {
            InstalledApp app =
                new InstalledApp(getApplicationContext(), packageInfo.packageName);
            mAppList.add(app);
        }
    }
}
 
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initAppList();
    ArrayAdapter adapter =
     new InstalledAppAdapter(getApplicationContext(), R.layout.app_item, mAppList);
 
    final ListView appListView = (ListView) findViewById(R.id.app_list);
    appListView.setAdapter(adapter);
    appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id) {
            InstalledApp app = mAppList.get(position);
            boolean lockStatus = app.isLocked();
            app.setLocked(!lockStatus);
 
            ImageView appLockStatus = (ImageView) view.findViewById(R.id.app_lock_status);
            appLockStatus.setImageDrawable(app.getLockStatusIcon());
 
            // notify AppLockerService
            Intent intent = new Intent(MainActivity.this, AppLockerService.class);
            intent.putExtra(app.isLocked() ? "ADD_APP" : "REMOVE_APP", 	app.getPackageName());
            startService(intent);
        }
    });
}

差不多就这么多内容,还有一些小细节比如如何避免解锁后再次加锁,还有开机自启等等,具体看看代码就清楚了。

最后附上代码链接,欢迎大家交流分享!

 源码下载

你可能感兴趣的:(Android应用)