Android 13 keyguard 覆盖 Occluded 流程

简介: 学习笔记

第一次接触 Occluded 是在唤起安全相机时,见Android 13 双击power键唤起安全相机。当时在这里遇到个bug:双击power键唤起安全相机,却弹出了bouncer界面,把安全相机界面遮挡了,后面就深入去看了下;看了几天都没找出问题,最后慢放复现视频,发现测试在安全相机界面有误触,根本没问题.......。

不过我们是退出锁屏还是进入锁屏,都将走这么个流程到 KeyguardController:

RootWindowContainer#ensureActivitiesVisible() -> DisplayContent#ensureActivitiesVisible() -> Task#ensureActivitiesVisible() -> ActivityTaskSupervisor#beginActivityVisibilityUpdate() -> KeyguardController#updateVisibility()

Occluded 的改变是从 system_server 进程中的 KeyguardController 开始的;

KeyguardController#updateVisibility()

// KeyguardController.java
    // 确保在完成设置所有可见性之前,根据需要更新锁定屏幕遮挡/关闭/打开屏幕状态
    void updateVisibility() {
        for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
             displayNdx >= 0; displayNdx--) {
            final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
            if (display.isRemoving() || display.isRemoved()) continue;
            final KeyguardDisplayState state = getDisplayState(display.mDisplayId);
            // 重点关注
            state.updateVisibility(this, display);
            if (state.mRequestDismissKeyguard) {
                handleDismissKeyguard(display.getDisplayId());
            }
        }
    }

state 为 KeyguardController 内部类KeyguardDisplayState 的对象;

KeyguardController#KeyguardDisplayState.updateVisibility()

// KeyguardController.java # KeyguardDisplayState.class
        void updateVisibility(KeyguardController controller, DisplayContent display) {
            final boolean lastOccluded = mOccluded;
            // 省略部分代码......
            mOccluded = false;
            final Task task = getRootTaskForControllingOccluding(display);
            final ActivityRecord top = task != null ? task.getTopNonFinishingActivity() : null;
            if (top != null) {
                // 省略部分代码......
                // 只有顶级 activity 可以控制遮挡,因为如果顶级应用不想遮挡键盘,我们就无法遮挡键盘。
                occludedByActivity = mTopOccludesActivity != null
                        || (mDismissingKeyguardActivity != null
                        && task.topRunningActivity() == mDismissingKeyguardActivity
                        && controller.canShowWhileOccluded(
                                true /* dismissKeyguard */, false /* showWhenLocked */));
                // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
                if (mDisplayId != DEFAULT_DISPLAY) {
                    occludedByActivity |= display.canShowWithInsecureKeyguard()
                            && controller.canDismissKeyguard();
                }
            }
            mShowingDream = display.getDisplayPolicy().isShowingDreamLw() && (top != null
                    && top.getActivityType() == ACTIVITY_TYPE_DREAM);
            mOccluded = mShowingDream || occludedByActivity;
            mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
                    && !mOccluded && !mKeyguardGoingAway
                    && mDismissingKeyguardActivity != null;
            // 省略部分代码......
            if (lastOccluded != mOccluded) {
                // 重点关注,当遮挡状态改变时调用
                controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
            } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
                controller.handleKeyguardGoingAwayChanged(display);
            }
        }

在该方法里对变量 mOccluded 进行了更新,完成了 KeyguardController 的 occluded 属性改变。

KeyguardController# handleOccludedChanged()

// KeyguardController.java
    private void handleOccludedChanged(int displayId, @Nullable ActivityRecord topActivity) {
        if (displayId != DEFAULT_DISPLAY) {
            updateKeyguardSleepToken(displayId);
            return;
        }
        // 重点关注
        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
        if (isKeyguardLocked(displayId)) {
            mService.deferWindowLayout();
            try {
                mRootWindowContainer.getDefaultDisplay()
                        .requestTransitionAndLegacyPrepare(
                                isDisplayOccluded(DEFAULT_DISPLAY)
                                        ? TRANSIT_KEYGUARD_OCCLUDE
                                        : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
                updateKeyguardSleepToken(DEFAULT_DISPLAY);
                mWindowManager.executeAppTransition();
            } finally {
                mService.continueWindowLayout();
            }
        }
        dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null
                ? topActivity.getRootTask() : null);
    }

mWindowManager为 WindowManagerService 对象,mPolicy 是 WindowManagerPolicy 接口的对象,而 WindowManagerPolicy 被 PhoneWindowManager 继承,实现了其相关方法;则上述代码将调用到:PhoneWindowManager#onKeyguardOccludedChangedLw()

// PhoneWindowManager.java
    @Override
    public void onKeyguardOccludedChangedLw(boolean occluded) {
        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()
                && !WindowManagerService.sEnableShellTransitions) {
            mPendingKeyguardOccluded = occluded;
            mKeyguardOccludedChanged = true;
        } else {
            // 重点关注
            setKeyguardOccludedLw(occluded, false /* force */,
                    false /* transitionStarted */);
        }
    }

这里当 mKeyguardDelegate.isShowing() 为 true 时,将会绕过 setKeyguardOccludedLw(),但是在后面的过渡动画会再调用 到setKeyguardOccludedLw(),该处过渡动画流程不作介绍。

直接看 PhoneWindowManager#setKeyguardOccludedLw():

// PhoneWindowManager.java
    // 更新 Keyguard 的遮挡状态
    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
            boolean transitionStarted) {
        if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
        mKeyguardOccludedChanged = false;
        if (isKeyguardOccluded() == isOccluded && !force) {
            return false;
        }
        final boolean showing = mKeyguardDelegate.isShowing();
        final boolean animate = showing && !isOccluded;
        // 当为keyguard((un)occlude 转换启用远程动画时,
        // KeyguardService 使用远程动画开始作为更新其遮挡状态的信号,因此我们不需要在这里通知。
        final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
                || !transitionStarted;
        // 重点关注
        mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
        return showing;
    }

这里改变了mKeyguardDelegate 的值,也就完成了PhoneWidnowManager 的 occluded 属性改变。

KeyguardServiceDelegate#setOccluded()

// KeyguardServiceDelegate.java
    public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
        if (mKeyguardService != null && notify) {
            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
            // 这里最终是调到 KeyguardService 的;
            mKeyguardService.setOccluded(isOccluded, animate);
        }
        mKeyguardState.occluded = isOccluded;
    }

KeyguardService#setOccluded()

// KeyguardService.java
        @Override // Binder interface
        public void setOccluded(boolean isOccluded, boolean animate) {
            Log.d(TAG, "setOccluded(" + isOccluded + ")");
            Trace.beginSection("KeyguardService.mBinder#setOccluded");
            checkPermission();
            // 重点关注
            mKeyguardViewMediator.setOccluded(isOccluded, animate);
            Trace.endSection();
        }

KeyguardViewMediator#setOccluded()

// KeyguardViewMediator.java
    /**
     * 当键盘被另一个窗口遮挡时通知我们
     */
    public void setOccluded(boolean isOccluded, boolean animate) {
        Log.d(TAG, "setOccluded(" + isOccluded + ")");
        Trace.beginSection("KeyguardViewMediator#setOccluded");
        if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
        mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
        mHandler.removeMessages(SET_OCCLUDED);
        Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
        // 重点关注
        mHandler.sendMessage(msg);
        Trace.endSection();
    }

上述代码通过 Handler 发送了一条消息,调用到 KeyguardViewMediator#handleSetOccluded()

// KeyguardViewMediator.java
    private void handleSetOccluded(boolean isOccluded, boolean animate) {
        Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
        Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
        synchronized (KeyguardViewMediator.this) {
            if (mHiding && isOccluded) {
                // We're in the process of going away but WindowManager wants to show a
                // SHOW_WHEN_LOCKED activity instead.
                // TODO(bc-unlock): Migrate to remote animation.
                startKeyguardExitAnimation(0, 0);
            }
            if (mOccluded != isOccluded) {
                mOccluded = isOccluded;
                mUpdateMonitor.setKeyguardOccluded(isOccluded);
                // 重点关注
                mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
                        && mDeviceInteractive);
                adjustStatusBarLocked();
            }
        }
        Trace.endSection();
    }

这里完成了 mOccluded 值的改变,并把值传到 StatusBarKeyguardViewManager。

KeyguardViewController 被 StatusBarKeyguardViewManager 继承,实现了其相关方法。则继续看:

StatusBarKeyguardViewManager #setOccluded():

// StatusBarKeyguardViewManager.java
    @Override
    public void setOccluded(boolean occluded, boolean animate) {
        final boolean isOccluding = !mOccluded && occluded;
        final boolean isUnOccluding = mOccluded && !occluded;
        setOccludedAndUpdateStates(occluded);
        if (mShowing && isOccluding) {
            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                    SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
            if (mCentralSurfaces.isInLaunchTransition()) {
                final Runnable endRunnable = new Runnable() {
                    @Override
                    public void run() {
                        ///M: [ALPS01807921]
                        ///   mOccluded may be changed before the runnable is executed.
                        if (mOccluded) {
                            mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
                            reset(true /* hideBouncerWhenShowing */);
                        } else {
                            Log.d(TAG, "setOccluded.run() - mOccluded was set to false") ;
                        }
                    }
                };
                mCentralSurfaces.fadeKeyguardAfterLaunchTransition(
                        null /* beforeFading */,
                        endRunnable,
                        endRunnable);
                return;
            }
         // 省略部分代码......
    }

至此,完成了从系统到SystemUI Occluded 值改变。

目录
打赏
0
0
0
0
138
分享
相关文章
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
223 28
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
301 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
111 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
282 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
129 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【03】优雅草央千澈详解关于APP签名以及分发-上架完整流程-第三篇安卓APP上架华为商店后面的步骤-华为应用商店相对比较麻烦一些-华为商店安卓上架
【03】优雅草央千澈详解关于APP签名以及分发-上架完整流程-第三篇安卓APP上架华为商店后面的步骤-华为应用商店相对比较麻烦一些-华为商店安卓上架
103 16
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
178 6
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
86 3
AI助理
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问

你好,我是AI助理

可以解答问题、推荐解决方案等