最近工作上要用到应用锁(AppLocker),早上装了个试玩了一下,下午花了2个小时捣鼓出来一个demo (基于android 6.0),简单分享一下。
免责声明:这篇文章纯粹是个人YY,未参考或反编译任何商业app。
所谓应用锁,说白了就是在监测到目标app启动时额外起一个锁屏界面把它盖住。这种方式的缺点是显而易见的,无法100%保证盖住,有时候会先闪出一个app启动界面,然后才被盖住。最彻底的方式当然是在framework里拦,不过这种方式需要跟平台厂商合作,不在今天的讨论范围之内。
先看一下效果,有两种盖法,一种是起一个新的activity,还有一种是起一个悬浮窗。我没有比较过这两种方式的优劣,先采用了第一种。悬浮窗之前封装了一个类,后面有时间再写文章分享出来。
下面讨论实现细节:
在早期的android版本中,大家都是通过getRunningTasks()来获取前台进程的,但是出于安全考虑这个API已经被google阉割了,要用这个API必须要申请REAL_GET_TASKS权限,但是这个权限又是signature or system的,所以基本上是然并卵。。。
<permission android:name="android.permission.REAL_GET_TASKS" android:permissionGroup="android.permission-group.APP_INFO" android:protectionLevel="signature|system" android:label="@string/permlab_getTasks" android:description="@string/permdesc_getTasks" />
在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<UsageStats> 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()方法里的两个参数0和ts表示统计从开机到当前时间所有app的使用情况,然后就是比较时间戳找到最近一次使用的app,返回它的package名称。
既然要持续监控前台进程,自然需要一个service,我们把它命名为AppLockerService。为避免阻塞主线程,这里创建一个新的HandlerThread,然后每隔300ms通过Handler发一个消息去检查前台进程,如果在监控列表中就启动锁屏界面(AppLockerActivity),通过在intent中添加一个APP_NAME字段表示被锁app的package名称。
另外有个需求是锁屏以后需要重新验证一次手势密码,因此需要通过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(); } }
private void initAppList() { List<PackageInfo> 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<InstalledApp> 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); } }); }
差不多就这么多内容,还有一些小细节比如如何避免解锁后再次加锁,还有开机自启等等,具体看看代码就清楚了。
最后附上代码链接,欢迎大家交流分享!