安卓开发-应用锁功能的实现

安卓开发-应用锁功能的实现

需求

​ 能够对指定的应用上锁,上锁之后启动这些应用需要先验证密码,密码验证通过才能打开。

注意:开发这个功能需要有源码环境,或者有改动系统级别应用的权限;下面的内容是在这两项前提下进行的,如果没有上面的条件,需要找下其他方法来实现。

思路分析

  • 启动应用实际上就是启动对应的Activity,要在应用启动之前做密码验证,可以在Activity启动过程中进行拦截。
  • 在启动一个Activity时,目标Activity的相关信息会封装在Intent中,系统也是根据Intent的信息去解析出要启动的Activity。所以可以根据Intent中携带的包名等信息去做判断和拦截。
  • 注意1:显式启动Activity的Intent中含有包名信息,但也存在隐式启动的情况,隐式启动Activity的Intent中是不包含包名信息的,这种情况下就需要想办法拿到待启动Activity所属的包名,否则无法判断该Activity是否需要拦截。
  • 需要写一个LockApp用来执行验证密码等操作,拦截Activity后,将原始的Intent转交给LockApp来处理,若是密码验证通过再使用这个Intent重新执行启动流程;若是密码验证失败就不启动应用。
  • 注意2:Activity在启动过程中,原始的Intent是会被更改的,在解析过程中,会加入一些Flag等信息,被修改后的Intent无法再用来重新启动对应的Activity。所以要在合适的地方,拿到原始的Intent传递给LockApp。
  • 在拦截时,需要排除同一个应用内,Activity相互启动的情况。如某App有ActivityA和ActivityB,用户启动app默认打开ActivityA,这时候已经验证过密码了,随后用户在app内点击跳转到ActivityB时,就不应该再对ActivityB进行拦截。
  • 在拦截时,需要排除LockApp的请求。LockApp不会主动去启动App,来自LockApp的启动请求,已经通过密码验证了,需要正常启动。

方法一:在Activity启动源码中拦截

​ 经过分析,在Activity启动过程中,会调用ActivityStarter.execute()方法,在这个方法内部可以同时拿到原Intent和解析后的包名信息,所以可以在这个方法中做处理:

// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java

// 关键方法,其他代码省略
int execute() {
    try {
        // add by lll --AppLock -2023/12/08+++
        // 在方法的开头通过mRequest.intent拿到原Intent
        Intent originalIntent = mRequest.intent;
        // end add by lll --AppLock -2023/12/08---
        
        
        // ...
        // 在这里解析原Intent,要在这之前保存一下原Intent
        if (mRequest.activityInfo == null) {
            mRequest.resolveActivity(mSupervisor);
        }

        // ...
        // 解析完成后,会将信息保存在mRequest.resolveInfo中
        if (mRequest.intent != null && mRequest.resolveInfo != null) {
            // 将要启动Activity的包名
            android.util.Log.d(TAG, "execute:  mRequest.resolveInfo package =" + mRequest.resolveInfo.activityInfo.packageName);
            // action信息
            android.util.Log.d(TAG, "execute:  intentAction " + mRequest.intent.getAction());
            // Activity的调用者
            android.util.Log.d(TAG, "execute:  callingPackage " + mRequest.callingPackage);
            

            // add by lll --AppLock -2023/12/08+++
            // 需要满足两个条件:
            // 1. 启用了应用锁,这个属性可以作为应用锁的总开关
            // 2. 不同应用间的启动才需要验证密码,同一个应用的Activity互相启动需要跳过
            if (SystemProperties.getBoolean("persist.sys.app.lock.state", false) 
                	&& !originalIntent.toString().contains(mRequest.callingPackage)) {
                String check_packageName = mRequest.resolveInfo.activityInfo.packageName;
                // 通过包名判断是否要拦截处理,这里只写了一个app,可以拓展到多个应用
                if ("com.xxx.settings".equals(check_packageName)) {
                    android.util.Log.d(TAG, "execute:  startActivity-");
                    Intent interceptIntent = new Intent();
                    interceptIntent.setComponent(new ComponentName("com.xxx.appLock", "com.xxx.appLock.MainActivity"));
               
                    // 将原Intent传给LockApp
                    interceptIntent.putExtra("originalIntent", originalIntent);
                    interceptIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    // 启动LockApp
                    mService.getUiContext().startActivity(interceptIntent);;
                    return START_ABORTED;
                }
            }
            // end add by lll --AppLock -2023/12/08---

        }
        	// ...
            return getExternalResult(res);
        }
    } finally {
        onExecutionComplete();
    }
}

LockApp的关键代码:

/**
 * 验证密码
 */
public class FirstFragment extends Fragment {
    private Activity mActivity;
    private Intent mIntent;

    // ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mActivity = getActivity();
        // 拿到ActivityStarter传过来的原Intent
        mIntent = (Intent) mActivity.getIntent().getParcelableExtra("originalIntent");
        init(view);
    }

    private void init(View view) {
        // ...
        NumberKeypad.Listener numberListener = new NumberKeypad.Listener() {
            @Override
            public void onFinish(String password) {
                Log.d(TAG, "onFinish: " + password);
                new Handler().postDelayed(() -> {
                    if (password.equals(SystemProperties.get("persist.sys.safety.lock.password", ""))) {
                        // 密码验证通过
                        Log.d(TAG, "run: intent " + mIntent);
                        if (mIntent != null) {
                            // 这里最好是加上FLAG_ACTIVITY_NEW_TASK,否则启动的Activity会跟验证密码的app在同一个栈中
                    	    mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(mIntent);
                        }
                        // 关闭密码验证页面
                        System.exit(0);
                    } else {
                        Toast.makeText(getActivity(), getString(R.string.password_wrong), Toast.LENGTH_SHORT).show();
                        numberKeypad.reset();
                    }
                }, 50);
            }
        };
    }
}

方法二:继承IActivityController.Stub来监控Activity的状态

​ IActivityController.aidl是系统自带的aidl,在Am的内部类MyActivityController有实现这个aidl接口,主要用于app状态监听控制。继承IActivityController.Stub需要实现六个方法:

  • activityStarting:当系统正在启动一个activity时会触发,当返回true,表示允许启动。当返回状态noraml/false分别表示停止/拒绝启动activity。
  • activityResuming:当系统正在返回一个activity时会触发,当返回true,表示允许返回。当返回状态noraml/false分别表示停止/拒绝返回activity
  • appCrashed:当一个应用进程已经崩溃会触发,当返回true时,表示可以重启,当返回false时,表示立即杀死它(进程)。
  • appEarlyNotResponding:系统检测到一个应用程序可能即将变得无响应,但在达到正式的ANR状态之前。这给了应用程序一个机会去恢复,或者让系统进行进一步的检查以确认是否真的无响应
  • appNotResponding:当一个应用进程出现ANR时就会触发,当返回0时,表示会弹出应用无响应的dialog,如果返回1时,表示继续等待,如果返回-1时,表示立即杀死进程。
  • systemNotResponding:当系统检测到整个系统似乎无响应时,会调用此方法。如果返回1,表示继续等待,如果返回-1,就让系统进行正常的自杀

​ 由上面的内容可知:可以通过自定义ActivityController类,重写activityStarting方法来监听Activity的启动

使用:

​ 只是继承IActivityController.Stub接口重写activityStarting方法,还不能让系统在启动Activity时触发对应的方法,需要将我们自定义的ActivityController类注册到系统中:在系统级别应用中调用IActivityManager.setActivityController()方法来设置我们自定义的ActivityController:

// 这里选择的是Launcher的onCreate中
@Override
protected void onCreate(Bundle savedInstanceState) {
    lastTime = SystemClock.currentThreadTimeMillis();
    super.onCreate(savedInstanceState);
    this.savedInstanceState = savedInstanceState;
    mContext = this;
    setContentView(R.layout.activity_main);

 	// 将我们自定义的ActivityController类注册到系统中
    IActivityManager am = ActivityManagerNative.getDefault();
    try {
        Log.d(TAG, "setActivityController: ");
        am.setActivityController(new ActivityController(this),true);
    } catch (RemoteException e) {
        Log.d(TAG, "setActivityController: error " + e.getMessage());
        e.printStackTrace();
    }
}

​ 这里需求是拦截应用的启动,所以重点关注activityStarting方法即可,自定义的ActivityController类实现如下:

/**
 * @author liang
 * @description: ActivityController
 * @date 2024/6/6 8:39
 */
public class ActivityController extends IActivityController.Stub {

    private Context mContext;

    public ActivityController(Context context) {
        mContext = context;
    }

    /**
     * 当系统正在启动一个activity时会触发
     * @author liang
     * @createTime 2024/6/6 8:40
     * @param intent: 启动Activity的Intent
     * @param pkg: 将要启动Activity所属的包名
     * @return : boolean:当返回true,表示允许启动。当返回状态noraml/false分别表示停止/拒绝启动activity
     */
    @Override
    public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
        // 应用锁已启用
        if (SystemProperties.getBoolean("persist.sys.app.lock.state", false)){
            Log.d(TAG, "activityStarting: intent " + intent);
        	Log.d(TAG, "activityStarting: pkg " + pkg);
            // 是否从LockApp启动,由LockApp启动的不需要拦截
        	boolean openByLockApp = SystemProperties.getBoolean("persist.sys.open.by.lock.app", false);
        	// 该应用是否在前台,在前台表示是同一个应用的启动,无需拦截
        	boolean appOpened = appInForeground(pkg);
        	Log.d(TAG, "activityStarting: openByLockApp " + openByLockApp + "; appOpened " + appOpened);

            // 由LockApp启动或由同一个应用启动的 不需要拦截
            if (!openByLockApp && !appOpened) {
                // 通过包名判断是否要拦截处理,这里只写了一个app,可以拓展到多个应用
                if ("com.xxx.settings".equals(pkg)) {
                    Log.d(TAG, "activityStarting: intercept " + pkg);
                    Intent interceptIntent = new Intent();
                    interceptIntent.setComponent(new ComponentName("com.xxx.appLock", "com.xxx.appLock.MainActivity"));
                    // 将原Intent传给LockApp
                    interceptIntent.putExtra("originalIntent", intent);
                    interceptIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    // 启动LockApp
                    mContext.startActivity(interceptIntent);
                    SystemProperties.setString("persist.sys.open.by.lock.app", "false");
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public boolean activityResuming(String pkg) throws RemoteException {
        return true;
    }

    @Override
    public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) throws RemoteException {
        return true;
    }

    @Override
    public int appEarlyNotResponding(String processName, int pid, String annotation) throws RemoteException {
        return 0;
    }


    @Override
    public int appNotResponding(String processName, int pid, String processStats) throws RemoteException {
        return 0;
    }


    @Override
    public int systemNotResponding(String msg) throws RemoteException {
        return 0;
    }
}

​ activityStarting(Intent intent, String pkg)方法传入两个参数分别是启动Activity的Intent,和Activity的数所包名。根据传进来的pkg参数判断该Activity是否需要拦截;拦截之后,将启动Activity的intent传递给LockApp即可。后续的处理于方法一类似,这里不再列出LockApp的代码。

​ 相比第一种方式,这里少了一个关键信息:启动Activity的app的包名(调用者的包名,不是被启动Activity的包名),所以不能直接判断是不是由同一个应用启动,也不能直接判断是不是由LockApp启动的。所以这里需要用额外的方式去处理:

  • 判断被启动的app是否在前台,在前台的话意味着是同一个应用内的Activity,无需拦截。
  • 使用一个标志来标志Activity是否由LockApp启动,由LockApp启动的应用,被视为是已经通过密码验证,不需要拦截。需要在LockApp中做处理:
    • 启动LockApp时设置persist.sys.open.by.lock.app为false
    • 密码验证正确、重新启动Activity时,设置persist.sys.open.by.lock.app为true

方式二补充说明

activityStarting()方法的调用,其实也是在frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java这个文件中的,并且就是在方式一处理的时间节点后面一点点,源码如下:

int execute() {
    try {
        // ...
        // 解析Intent
        if (mRequest.activityInfo == null) {
            mRequest.resolveActivity(mSupervisor);
        }

        // 尝试为关闭或重启添加检查点,记录原始Intent和包名
        if (mRequest.intent != null) {
            String intentAction = mRequest.intent.getAction();
            String callingPackage = mRequest.callingPackage;
            if (intentAction != null && callingPackage != null
                    && (Intent.ACTION_REQUEST_SHUTDOWN.equals(intentAction)
                            || Intent.ACTION_SHUTDOWN.equals(intentAction)
                            || Intent.ACTION_REBOOT.equals(intentAction))) {
                ShutdownCheckPoints.recordCheckPoint(intentAction, callingPackage, null);
            }
        }
        
        int res;
        synchronized (mService.mGlobalLock) {
            // ...
            // 调用executeRequest方法
            res = executeRequest(mRequest);
            // ...
        }
    } finally {
        onExecutionComplete();
    }
}

private int executeRequest(Request request) {
    // ...
    // 这里拿到的Intent是修改后的intent
    Intent intent = request.intent;
    
    // 这里的mService是ActivityTaskManagerService,mController是其内部类型为IActivityController的变量
    if (mService.mController != null) {
        try {
            // 提供给观察者的Intent已经剥离了额外的数据,拿到的是原Intent,可以直接转发使用
            Intent watchIntent = intent.cloneFilter();
            // 调用IActivityController的activityStarting方法
            abort |= !mService.mController.activityStarting(watchIntent,
                    aInfo.applicationInfo.packageName);
        } catch (RemoteException e) {
            mService.mController = null;
        }
    }
    // ...
}

​ 前面方式一有说到,启动Activity的过程中,Intent会被加入一些Flag参数之类的,被修改后的Intent无法再次用来启动Activity。executeRequest()方法的调用在解析Intent之后,所以在executeRequest()方法中拿到的Intent是修改后的intent。
​ 调用activityStarting之前,通过intent.cloneFilter()拿到的Intent,是已经剥离了额外的数据,相当于是原Intent,所以可以用来重新启动Activity。

​ 再看下 将自定义ActivityController类注册到系统中的代码:

// 将我们自定义的ActivityController类注册到系统中
IActivityManager am = ActivityManagerNative.getDefault();
try {
    Log.d(TAG, "setActivityController: ");
    am.setActivityController(new ActivityController(this),true);
} catch (RemoteException e) {
    Log.d(TAG, "setActivityController: error " + e.getMessage());
    e.printStackTrace();
}

简单来说就是调用了IActivityManager的setActivityController()方法,追踪源码,发现IActivityManager的setActivityController()方法的调用逻辑如下:

// frameworks/base/core/java/android/app/IActivityManager.aidl
void setActivityController(in IActivityController watcher, boolean imAMonkey);

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public void setActivityController(IActivityController controller, boolean imAMonkey) {
    if (controller != null) {
        Binder.allowBlocking(controller.asBinder());
    }
    mActivityTaskManager.setActivityController(controller, imAMonkey);
}

// frameworks/base/core/java/android/app/IActivityTaskManager.aidl
void setActivityController(in IActivityController watcher, boolean imAMonkey);

// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public void setActivityController(IActivityController controller, boolean imAMonkey) {
    mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
            "setActivityController()");
    synchronized (mGlobalLock) {
        // 这里拿到的mController就是ActivityStarter.java里面的mService.mController
        mController = controller;
        mControllerIsAMonkey = imAMonkey;
        // 设置Watchdog中的IActivityController
        Watchdog.getInstance().setActivityController(controller);
    }
}

​ 所以到这里,逻辑就联系起来了,在我们的应用中调用了IActivityManager的setActivityController()方法,实际上就是将自定义的ActivityController对象传递到ActivityTaskManagerService中,然后在Activity启动过程中,会回调我们重写的setActivityController方法()。

小结

​ 上面提供了两种实现应用锁的思路,可能还有一些其他的方法可以实现同样的效果,但核心的逻辑还是通过packageName去过滤应用,然后重新启动Activity。这里只是简单介绍了应用锁的思路和实现步骤,具体的原理没有讲解,有能力的同学可以尝试分析下framework中Activity启动过程的源码,这样就可以理解为什么是在ActivityStarter.execute()方法中添加拦截的逻辑。

​ 与第一种方式相比,第二种方法的优点是不需要改动framework源码,只在系统的app代码中就可以实现,并且比较方便地管理上锁的应用(在应用内使用List动态管理);缺点是缺少关键信息:启动Activity的应用的信息,所以就要做更多的逻辑去判断各种情况,这也意味着维护成本的增加。两种方式各有优劣,可以根据自己的使用场景去做选择。

​ 由于个人能力有限,上面的分析和讲解难免有错漏之处,欢迎各位批评指正;如有其他比较好的实现方法,同时也欢迎大家相互交流学习,一起进步,共勉!!!

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Android应用实现指纹解功能,您需要遵循以下步骤: 1. 检查设备是否支持指纹解功能: 在应用程序代码中,您可以使用`FingerprintManager`类来检查设备是否支持指纹解功能。您可以使用`isHardwareDetected()`方法检测指纹传感器是否可用。 2. 请求指纹权限: 在AndroidManifest.xml文件中,确保您的应用程序声明了`USE_FINGERPRINT`权限,以便能够使用指纹解功能。 3. 创建指纹识别回调: 您需要创建一个实现`FingerprintManager.AuthenticationCallback`接口的回调类,以处理指纹识别结果。在回调类中,您可以处理成功或失败的情况,并采取相应的操作。 4. 初始化指纹管理器: 在您的Activity或Fragment中,您需要获取指纹管理器的实例。您可以使用`FingerprintManager`类的`getInstance()`方法来获取实例。 5. 开始指纹识别: 调用指纹管理器的`authenticate()`方法来开始指纹识别过程。您可以通过传递一个`CryptoObject`对象来增加安全性。在指纹识别过程中,系统将会弹出一个对话框来提示用户使用指纹传感器。 6. 处理指纹识别结果: 在您的指纹识别回调类中,实现`onAuthenticationSucceeded()`和`onAuthenticationFailed()`方法来处理指纹识别的成功和失败情况。您可以在这些方法中执行相应的操作,例如解设备或显示错误消息。 请注意,实现指纹解功能涉及的具体步骤可能因Android版本和设备厂商而有所不同。建议您查阅Android官方文档和相关开发资源以获取更详细的指导和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值