android10.0(Q) AOSP 增加应用锁功能

简介: android10.0(Q) AOSP 增加应用锁功能

前言


应用锁的功能可以说是很普遍了,大致就是在 startActivity 对应代码处进行拦截就行。

最开始在网上找了点资料,没有能合适直接用的,就自己搞了下,这里简单做个笔记。

Android应用锁实现

那就给大伙先来个效果图先康康


image.png


思路分析


由于我们的目标应用是系统 Settings ,这家伙的入口不唯一,一开始是想着在 Launcher3 中进行拦截就行,


最终效果不太完美,后来改到 ActivityStarter 中 startActivity()


是怎么找到这个地方的呢?因为之前改过一个 Q 版本以上不能后台拉起 Activity 的问题,当时报错的地方


就在这里,后来想了下报错不就是被拦截了么,这样我们也可以在此处定制密码进行拦截。


1、找到拦截启动位置,startActivity()


2、读取要拉起 Activity 对应包名和需要加锁 APP 包名判断


3、在加锁清单中,弹出验证界面,验证成功,继续执行 startActivity


4、无需加锁,直接放行


上代码


1、Launcher3 中可拦截的地方


packages\apps\Launcher3\src\com\android\launcher3\Launcher.java

public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
            @Nullable String sourceContainer) {
        if (TestProtocol.sDebugTracing) {
            android.util.Log.d(TestProtocol.NO_START_TAG,
                    "startActivitySafely outer");
        }
         //cczheng add testcode
        final String packageName = intent.getComponent().getPackageName();
        android.util.Log.i("ActivityStarter111","packageName="+packageName);
        if ("com.android.settings".equals(packageName)) {
          //android.widget.Toast.makeText(this, "foo", 1000).show();
          //return true;
        }//end
        if (!hasBeenResumed()) {
            // Workaround an issue where the WM launch animation is clobbered when finishing the
            // recents animation into launcher. Defer launching the activity until Launcher is
            // next resumed.
            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
            return true;
        }
        boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
        if (success && v instanceof BubbleTextView) {
            // This is set to the view that launched the activity that navigated the user away
            // from launcher. Since there is no callback for when the activity has finished
            // launching, enable the press state and keep this reference to reset the press
            // state when we return to launcher.
            BubbleTextView btv = (BubbleTextView) v;
            btv.setStayPressed(true);
            addOnResumeCallback(btv);
        }
        return success;
    }


在此处判断包名后可直接 new 对话框进行交互,优点方便简单,缺点不能完全加锁。


2、ActivityStarter 中可拦截的地方


我这里偷懒了,界面没啥美化可言,你们自己按需调整。这里面引入 R 资源估计会有问题,所以这里直接


java 代码写布局了,通过 window 方式展示。


要拦截的包名清单可以通过 ContentProvider 查询,毕竟有 Context,自己按需增加。


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

import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.InputType;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
    //cczheng add for appLockPass S
    boolean isLocked;
    boolean isPassCheck;
    private void showAppLockPasswordWindow(Context mContext, IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
        final WindowManager.LayoutParams params =  new WindowManager.LayoutParams();
        params.width = 380;
        params.height = 230;
        params.format = Color.parseColor("#ADADAD");
        params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
        params.setTitle("AppLock");
        WindowManager mWM = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        final LinearLayout parentLayout = new LinearLayout(mContext);
        parentLayout.setOrientation(LinearLayout.VERTICAL);
        parentLayout.setBackgroundColor(Color.WHITE);
        LinearLayout.LayoutParams layoutParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
        parentLayout.setLayoutParams(layoutParams);
        TextView titleText = new TextView(mContext);
        LinearLayout.LayoutParams contentParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        titleText.setLayoutParams(contentParams);
        titleText.setText("Please enter app password");
        titleText.setTextColor(Color.BLACK);
        titleText.setTypeface(Typeface.create(titleText.getTypeface(), Typeface.NORMAL), Typeface.BOLD);
        titleText.setPadding(10, 10, 0, 0);
        parentLayout.addView(titleText);
        EditText passText = new EditText(mContext);
        passText.setLayoutParams(contentParams);
        passText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        passText.setTextColor(Color.BLACK);
        parentLayout.addView(passText);
        Button okBtn = new Button(mContext);
        LinearLayout.LayoutParams btnParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        btnParams.gravity = Gravity.RIGHT;
        okBtn.setLayoutParams(btnParams);
        okBtn.setBackgroundColor(Color.TRANSPARENT);
        okBtn.setText("Confirm");
        okBtn.setTextColor(Color.parseColor("#3996E8"));
        okBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String password = passText.getText().toString();
                //android.widget.Toast.makeText(mContext, password, 1000).show();
                if (parentLayout!=null){
                    mWM.removeViewImmediate(parentLayout);
                    //parentLayout = null;
                }
                if ("123".equals(password)) {
                    isPassCheck = true;
                    startActivity(caller, intent, ephemeralIntent,
                             resolvedType, aInfo, rInfo,
                             voiceSession,  voiceInteractor,
                             resultTo,  resultWho,  requestCode,  callingPid,  callingUid,
                             callingPackage,  realCallingPid,  realCallingUid,  startFlags,
                             options,
                             ignoreTargetSecurity,  componentSpecified,  outActivity,
                             inTask,  allowPendingRemoteAnimationRegistryLookup,
                             originatingPendingIntent,  allowBackgroundActivityStart);
                }else {
                    isPassCheck = false;
                }
            }
        });
        parentLayout.addView(okBtn);
        try {
            mWM.addView(parentLayout, params);
        } catch (WindowManager.BadTokenException e) {
            e.printStackTrace();
        }
    }
    IApplicationThread mscaller; 
    Intent msintent, msephemeralIntent;
    String msresolvedType, msresultWho, mscallingPackage;
    ActivityInfo msaInfo;
    ResolveInfo msrInfo;
    int msrequestCode, mscallingPid, mscallingUid, msrealCallingPid, msrealCallingUid, msstartFlags;
    boolean msignoreTargetSecurity, mscomponentSpecified, msallowPendingRemoteAnimationRegistryLookup, msallowBackgroundActivityStart;
    IVoiceInteractionSession msvoiceSession;
    IVoiceInteractor msvoiceInteractor;
    IBinder msresultTo;
    SafeActivityOptions msoptions;
    ActivityRecord[] msoutActivity;
    TaskRecord msinTask;
    PendingIntentRecord msoriginatingPendingIntent;
    //E
    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
        mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
        int err = ActivityManager.START_SUCCESS;
        // Pull the optional Ephemeral Installer-only bundle out of the options early.
        final Bundle verificationBundle
                = options != null ? options.popAppVerificationBundle() : null;
      ................................
    checkedOptions = mInterceptor.mActivityOptions;
        }
        //cczheng add for appLockPass S
        android.util.Log.e("ActivityStarter","callingPackage="+callingPackage);
        final String packageName = intent.getComponent().getPackageName();
        android.util.Log.i("ActivityStarter","packageName="+packageName+" abort="+abort);
    if (("com.android.launcher3".equals(callingPackage) || "com.android.systemui".equals(callingPackage)) 
        && "com.android.settings".equals(packageName)) {
      isLocked = true;
    }else{
      isLocked = false;
    }
    if (isPassCheck) {
      android.util.Log.i("ActivityStarter","isPassCheck pass goto activity");
      isLocked = false;
      isPassCheck = false;
    }
      mscaller = caller;
      msintent = intent;
      msephemeralIntent = ephemeralIntent;
      msresolvedType = resolvedType;
      msaInfo = aInfo;
      msvoiceSession = voiceSession;
      msvoiceInteractor = voiceInteractor;
      msresultTo = resultTo;
      msresultWho = resultWho;
      msrequestCode = requestCode;
      mscallingPid = callingPid;
      mscallingUid = callingUid;
      mscallingPackage = callingPackage;
      msrealCallingPid = realCallingPid;
      msrealCallingUid = realCallingUid;
      msstartFlags = startFlags;
      msoptions = options;
      msignoreTargetSecurity = ignoreTargetSecurity;
      mscomponentSpecified = componentSpecified;
      msoutActivity = outActivity;
      msinTask = inTask;
      msallowPendingRemoteAnimationRegistryLookup = allowPendingRemoteAnimationRegistryLookup;
      msoriginatingPendingIntent = originatingPendingIntent;
      msallowBackgroundActivityStart = allowBackgroundActivityStart;
      new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
                if (isLocked) {
                    showAppLockPasswordWindow(mService.mContext, mscaller, msintent, msephemeralIntent,
                             msresolvedType, msaInfo, msrInfo,
                             msvoiceSession,  msvoiceInteractor,
                             msresultTo,  msresultWho,  msrequestCode,  mscallingPid,  mscallingUid,
                             mscallingPackage,  msrealCallingPid,  msrealCallingUid,  msstartFlags,
                             msoptions,
                             msignoreTargetSecurity,  mscomponentSpecified,  msoutActivity,
                             msinTask,  msallowPendingRemoteAnimationRegistryLookup,
                             msoriginatingPendingIntent,  msallowBackgroundActivityStart);
                }
            }
        });
       if (isLocked) {
           android.util.Log.d("ActivityStarter","START_SWITCHES_CANCELED=");
          return ActivityManager.START_SWITCHES_CANCELED;
       }///add EE
        if (abort) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
                        RESULT_CANCELED, null);
            }
            // We pretend to the caller that it was really started, but
            // they will just get a cancel result.
            ActivityOptions.abort(checkedOptions);
            return START_ABORTED;
        }
    ........................


以上几个坑点说一下


callingPackage 是调用 startActivity 方法的初始者,可以看到我这里加了过滤,必须是 SystemUI 和 Launcher3


调用的情况下才进行拦截,系统启动时我们看到的安卓正在启动中就是 Settings 中的 FallbackHome,此时 callingPackage 为 null


这样会卡住进不去系统,所以需要过滤。


showAppLockPasswordWindow() 需要在 Handler 执行,ActivityStarter 中不能直接进行 UI 线程操作,不然会报错。


符合拦截条件时需要先将此次 startActivity return,异步去显示 UI 操作,根据密码结果再递归一次 startActivity


所以 showAppLockPasswordWindow() 放到了内部类中,传递参数需要为 final 或者全局变量,所以上面加了一大堆全局赋值的,参数也太多了点。

目录
相关文章
|
7天前
|
存储 安全 Android开发
"解锁Android权限迷宫:一场惊心动魄的动态权限请求之旅,让你的应用从平凡跃升至用户心尖的宠儿!"
【8月更文挑战第13天】随着Android系统的更新,权限管理变得至关重要。尤其从Android 6.0起,引入了动态权限请求,增强了用户隐私保护并要求开发者实现更精细的权限控制。本文采用问答形式,深入探讨动态权限请求机制与最佳实践,并提供示例代码。首先解释了动态权限的概念及其重要性;接着详述实现步骤:定义、检查、请求权限及处理结果;最后总结了六大最佳实践,包括适时请求、解释原因、提供替代方案、妥善处理拒绝情况、适应权限变更及兼容旧版系统,帮助开发者打造安全易用的应用。
17 0
|
1天前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
|
3天前
|
存储 前端开发 Java
Android MVVM框架详解与应用
在Android开发中,随着应用复杂度的增加,如何有效地组织和管理代码成为了一个重要的问题。MVVM(Model-View-ViewModel)架构模式因其清晰的结构和高效的开发效率,逐渐成为Android开发者们青睐的架构模式之一。本文将详细介绍Android MVVM框架的基本概念、优势、实现流程以及一个实际案例。
14 0
|
8天前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
28 4
|
7天前
|
编解码 数据可视化 定位技术
Android平台GB28181记录仪在铁路可视化巡检应用
GB28181记录仪在铁路可视化巡检中,集成实时音视频采集、位置上报、语音通信与无线传输技术,确保巡检高效准确。它能实时记录巡检细节,支持高清画质,并通过北斗/GPS实现精确位置追踪。记录仪兼容多种视频与音频格式,具备音量调节与编码参数配置功能,支持横竖屏及后台服务推流。此外,它还能添加动态水印,确保数据完整性,并允许指挥中心远程下载与回放历史视频,全面满足铁路巡检需求。
|
7天前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
8天前
|
Android开发
解决android apk安装后出现2个相同的应用图标
解决android apk安装后出现2个相同的应用图标
48 2
|
8天前
|
编解码 Android开发 UED
【性能狂飙!】揭秘Android应用极速变身秘籍:内存瘦身+用户体验升级,打造丝滑流畅新境界!
【8月更文挑战第12天】构建高效Android应用需全方位优化,尤其重视内存管理和用户体验。通过弱引用降低内存占用,懒加载资源减少启动负担。运用Kotlin协程确保UI流畅不阻塞,响应式设计适配多屏需求。这些策略共同提升了应用性能与用户满意度。
18 1
|
2天前
|
JSON Android开发 C++
Android c++ core guideline checker 应用
Android c++ core guideline checker 应用
|
5天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
在数字时代,选择正确的开发平台是成功的一半。本文深入探讨了安卓与iOS两大移动操作系统的开发差异,并分析了各自对创新、用户体验和市场需求的响应。通过比较两者的设计哲学、开发工具、市场覆盖和用户参与度,我们揭示了每个平台的独特优势和潜在挑战,旨在为开发者提供决策时的洞见,帮助他们在竞争激烈的应用市场中做出明智的选择。