最近工作上要用到应用锁(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()方法里的两个参数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 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);
}
});
}
差不多就这么多内容,还有一些小细节比如如何避免解锁后再次加锁,还有开机自启等等,具体看看代码就清楚了。
最后附上代码链接,欢迎大家交流分享!