[mobilesafe] 01_程序锁细节

Android 4.0

程序锁细节

步骤:1、在需要验证密码的时候带上应用程序名称和应用程序图标
2、需要验证密码才能进入应用程序
3、如果成功,那么进入,并加入到临时不受保护的集合中,通知看门狗不保护
4、如果不成功或为null,提示密码错误
5、用户按后退键,应该回退到桌面
核心代码:
a) EnterPasswordActivity.java
package cn.zengfansheng.mobilesafe;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import cn.zengfansheng.mobilesafe.utils.ToastUtils;
/**
 * 19、程序管理-输入密码的Activity,输入正确才让进去
 * 
 * @author hacket
 */
public class EnterPasswordActivity extends Activity {
    private TextView tv_protect_name_icon;// 要被保护的icon和name
    private EditText et_enter_password;// 密码输入框
    private Button bt_enter;// 进入按钮
    private String packagename;// 要被保护的应用程序的包名
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_app_lock_enter_pwd);
        
        tv_protect_name_icon = (TextView) this.findViewById(R.id.tv_protect_name_icon);
        et_enter_password = (EditText) this.findViewById(R.id.et_enter_password);
        bt_enter = (Button) this.findViewById(R.id.bt_enter);
        
        // 1、设置EnterPasswordActivity中界面的布局
        setEnterPassActivity();
        // 2、为按钮设置点击事件
        bt_enter.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                String et_pass = et_enter_password.getText().toString().trim();
                if (TextUtils.isEmpty(et_pass)) {// 输入的密码为空,TODO 可以设置抖动的动画
                    ToastUtils.showToastInThread(EnterPasswordActivity.this"密码不能为空~~~");
                    return;
                }
                if ("123".equals(et_pass)) {//假设密码是123, TODO 这里可以从数据库或者SharedPreferences中获取,看你程序锁密码保存在哪
                    // 通知看门狗服务,不要再监视这个应用,暂时取消保护,也就是Activity和Service通信
                    // 解决:1、可以通过service和activity绑定进行 2、发送自定义广播
                    Intent intent = new Intent();
                    intent.setAction("cn.zengfansheng.cancel");
                    intent.putExtra("packagename"packagename);
                    sendBroadcast(intent);// 发送自定义广播
                    finish();// 销毁当前的Activity
                } else {
                    ToastUtils.showToastInThread(EnterPasswordActivity.this"密码不对~~~");
                    return;
                }
            }
        });
    }
    // 2、屏蔽掉用户按物理后退键
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 如果用户按了物理后退键,返回到桌面,可以系统定义,也可以看LogCat
        if (keyCode == KeyEvent.KEYCODE_BACK) {
        /*<intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.MONKEY"/>
        </intent-filter>*/
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_MAIN);
            // 防止不同的桌面,3个CATEGORY都设置
            intent.addCategory(Intent.CATEGORY_HOME);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.addCategory(Intent.CATEGORY_MONKEY);
            startActivity(intent);
            return true;// 返回true,消耗掉该事件
        }
        return super.onKeyDown(keyCode, event);
    }
    /**
     * 1、设置EnterPasswordActivity中界面的布局
     */
    private void setEnterPassActivity() {
        // 1、返回激活该Activity的Intent
        Intent intent = getIntent();//Return the intent that started this activity. 
        // 2、得到要保护的应用程序的包名
        packagename = intent.getStringExtra("packagename");
        // 3、得到包管理者
        PackageManager packageManager = getPackageManager();
        // 4、获取该应用的包名,获取该应用程序的名称和图标
        try {
            ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packagename, 0);
            String appName = applicationInfo.loadLabel(packageManager).toString();
            Drawable appIcon = applicationInfo.loadIcon(packageManager);
            appIcon.setBounds(0, 0, 70, 83);
            // 5、设置到要输入密码的Activity上
            tv_protect_name_icon.setCompoundDrawables(null, appIcon, nullnull);
            tv_protect_name_icon.setText(appName);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}
b) WatchDogService.java
package cn.zengfansheng.mobilesafe.service;
import java.util.ArrayList;
import java.util.List;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;
import cn.zengfansheng.mobilesafe.EnterPasswordActivity;
import cn.zengfansheng.mobilesafe.db.dao.AppLockDao;
/**
 * 4、看门狗Service——监听应用程序的启动
 * @author hacket
 */
public class WatchDogService extends Service {

    protected static final String TAG = "WatchDogService";
    private ActivityManager activityManager;// Activity管理者
    private boolean flag;// 服务运行的标记,true表示运行,false表示停止
    private AppLockDao appLockDao;// 程序锁的dao
    private Intent intent;// 进入输入密码Activity界面的Intent
    private CancelProtectApplication noProtectReceiver;// 暂时不保护的广播接收者
    private List<String> tempNoProtectAppPackageName;// 临时不受保护的应用的包名集合
    private BroadcastReceiver screenOffReceiver;// 屏幕锁屏的广播接收者
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        tempNoProtectAppPackageName = new ArrayList<String>();
        // 3、注册屏幕锁屏时的广播接收者
        screenOffReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // 屏幕锁屏时,将临时不受保护的List集合给清空
                if (tempNoProtectAppPackageName != null) {
                    tempNoProtectAppPackageName.clear();
                }
            }
        };
        IntentFilter screenFilter = new IntentFilter();
        screenFilter.addAction(Intent.ACTION_SCREEN_OFF);// 屏幕锁屏广播
        registerReceiver(screenOffReceiver, screenFilter);
        // 2、注册接收自定义的广播事件
        noProtectReceiver = new CancelProtectApplication();
        IntentFilter filter = new IntentFilter();
        filter.addAction("cn.zengfansheng.cancel");
        registerReceiver(noProtectReceiver, filter);
        // 1、获取ActivityManager管理者,进行开启看门狗服务
        activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        appLockDao = new AppLockDao(getApplicationContext());
        intent = new Intent(getApplicationContext(),EnterPasswordActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 外部开启Activity,一定要加上
        // 看门狗需要不停的监视后台正在运行的应用程序.
        new Thread() {
            public void run() {
                flag = true;
                while (flag) {
                    // 2、获取最近运行的3个Task
                    List<RunningTaskInfo> runningTasks = activityManager.getRunningTasks(3);
                    // 3、新打开的Activity在第一个task里面
                    RunningTaskInfo runningTaskInfo = runningTasks.get(0);
                    // 4、然后取栈顶,就是刚打开的Activity
                    ComponentName topActivity = runningTaskInfo.topActivity;
                    String packageName = topActivity.getPackageName();
                    boolean isLock = appLockDao.find(packageName);
                    if (isLock) {// 如果锁上了,进入输入密码Activity界面
                        if (!tempNoProtectAppPackageName.contains(packageName)) {// 如果临时不保护的集合不包含当前应用的包名,那么就要保护
                            Log.i(TAG, packageName + "锁上了~");
                            // 将要保护的packagename传递过去
                            intent.putExtra("packagename", packageName);
                            startActivity(intent);
                        }
                    } else {// 没有锁上
                        Log.i(TAG, packageName + "没有锁上~");
                    }
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(packageName);
                }
            }
        }.start();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        // 1、销毁时,服务不在运行
        flag = false;
        // 2、服务销毁时,取消自定义广播的事件-取消应用保护的注册
        unregisterReceiver(noProtectReceiver);
        noProtectReceiver = null;
        // 3、服务销毁时,取消屏幕广播事件的注册
        unregisterReceiver(screenOffReceiver);
        screenOffReceiver = null;
    }
    
    /**
     * 1、临时取消保护应用的广播接收者
     * @author hacket
     */
    private class CancelProtectApplication extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            // 1、 得到临时要取消保护的应用程序的包名
            String packagename = intent.getStringExtra("packagename");
            // 2、然后将其加入临时不受保护的List集合中去
            tempNoProtectAppPackageName.add(packagename);
        }
    }
}
问题1:用户输入正确后,进入应用,但看门狗,还是继续监视,又会弹出输入密码框输入,这个体验不好
解决:临时取消掉
定义一个临时集合容器,
输入正确时,不在监听该应用
问题2:当用户输入正确时,可以进去,当锁屏后再次输入不需要,
这个体验不好,最好是在锁屏的时候,将该临时保护的集合列表给清除,再下次进入时,需要输入密码
解决:
自定义发送广播事件
当用户输入正确后,临时取消保护
将该应用加入到一个临时不受保护的集合,在锁屏的时候,将该集合清除,
再下次屏幕开始时,又要输入密码
// 3、注册屏幕锁屏时的广播接收者
screenOffReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 屏幕锁屏时,将临时不受保护的List集合给清空
        if (tempNoProtectAppPackageName != null) {
            tempNoProtectAppPackageName.clear();
        }
    }
};
IntentFilter screenFilter = new IntentFilter();
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);// 屏幕锁屏广播
registerReceiver(screenOffReceiver, screenFilter);  

Service和Activity进行通信
通过自定义广播消息,发送给Service,Service接收到广播事件做相应的处理
问题3:如果用户在输入密码时,按后退键,会回到该应用,但又会被看门狗拦截,又会弹出密码框输入,
这个体验也不好
解决:如果用户按了物理后退键,回到桌面,设置按钮监听事件,监听后退键按下
桌面应用:
1)看logcat

2)看系统源码:桌面应用是如何定义的
<activity
            android:name="com.android.launcher2.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:theme="@style/Theme"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="nosensor">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
</activity>  
3)解决,
// 2、屏蔽掉用户按物理后退键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // 如果用户按了物理后退键,返回到桌面,可以系统定义,也可以看LogCat
    if (keyCode == KeyEvent.KEYCODE_BACK) {
    /*<intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.MONKEY"/>
    </intent-filter>*/
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_MAIN);
        // 防止不同的桌面,3个CATEGORY都设置
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.addCategory(Intent.CATEGORY_MONKEY);
        startActivity(intent);
        return true;// 返回true,消耗掉该事件
    }
    return super.onKeyDown(keyCode, event);
}