keyguard的occluded变化流程 - 安卓R

安卓keyguard的occluded属性介绍

keyguard的occluded属性为true时表示当前keyguard被遮挡了,也就是说当前即使手机还未解锁也不显示keyguard,即不显示NotificationShade窗口。例如锁屏相机、音乐app锁屏显示等。

实现Activity可以在锁屏显示的方法是在AndroidManifest.xml中的Activity标签中加上:android:showWhenLocked="true"即可。

安卓系统中有3个位置都有occluded属性,按occluded属性改变的顺序从先到后排序是:KeyguardController、PhoneWindowManager、SystemUI。首先occluded属性首先在system_server进程中的KeyguardController中改变,然后通知PhoneWindowManager,最后通过binder通知systemui进程来显示/隐藏NotificationShade。

KeyguardController的occluded属性改变流程

首先无论进入或退出锁屏显示的Activity都会调用frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java或frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java或frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java的ensureActivitiesVisible方法:

    /**
     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
     */
    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
            boolean preserveWindows, boolean notifyClients) {
        if (mStackSupervisor.inActivityVisibilityUpdate()) {
            // Don't do recursive work.
            return;
        }

        try {
            mStackSupervisor.beginActivityVisibilityUpdate();
            // First the front stacks. In case any are not fullscreen and are in front of home.
            for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
                final DisplayContent display = getChildAt(displayNdx);
                display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                        notifyClients);
            }
        } finally {
            mStackSupervisor.endActivityVisibilityUpdate();
        }
    }
    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
            boolean preserveWindows, boolean notifyClients) {
        mAtmService.mStackSupervisor.beginActivityVisibilityUpdate();
        try {
            for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = getStackAt(stackNdx);
                stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                        notifyClients);
            }
        } finally {
            mAtmService.mStackSupervisor.endActivityVisibilityUpdate();
        }
    }
    /**
     * Ensure visibility with an option to also update the configuration of visible activities.
     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
     * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
     * @param starting The top most activity in the task.
     *                 The activity is either starting or resuming.
     *                 Caller should ensure starting activity is visible.
     * @param notifyClients Flag indicating whether the visibility updates should be sent to the
     *                      clients in {@link mEnsureActivitiesVisibleHelper}.
     * @param preserveWindows Flag indicating whether windows should be preserved when updating
     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
     * @param configChanges Parts of the configuration that changed for this activity for evaluating
     *                      if the screen should be frozen as part of
     *                      {@link mEnsureActivitiesVisibleHelper}.
     */
    // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
            boolean preserveWindows, boolean notifyClients) {
        mTopActivityOccludesKeyguard = false;
        mTopDismissingKeyguardActivity = null;
        mStackSupervisor.beginActivityVisibilityUpdate();
        try {
            mEnsureActivitiesVisibleHelper.process(
                    starting, configChanges, preserveWindows, notifyClients);

            if (mTranslucentActivityWaiting != null &&
                    mUndrawnActivitiesBelowTopTranslucent.isEmpty()) {
                // Nothing is getting drawn or everything was already visible, don't wait for timeout.
                notifyActivityDrawnLocked(null);
            }
        } finally {
            mStackSupervisor.endActivityVisibilityUpdate();
        }
    }

最后都会调用frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java的endActivityVisibilityUpdate方法:

    /** Ends a batch of visibility updates. */
    void endActivityVisibilityUpdate() {
        mVisibilityTransactionDepth--;
        if (mVisibilityTransactionDepth == 0) {
            getKeyguardController().visibilitiesUpdated();
        }
    }

调用了frameworks/base/services/core/java/com/android/server/wm/KeyguardController.java的visibilitiesUpdated方法:

    /**
     * Makes sure to update lockscreen occluded/dismiss state if needed after completing all
     * visibility updates ({@link ActivityStackSupervisor#endActivityVisibilityUpdate}).
     */
    void visibilitiesUpdated() {
        boolean requestDismissKeyguard = false;
        for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
             displayNdx >= 0; displayNdx--) {
            final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
            final KeyguardDisplayState state = getDisplay(display.mDisplayId);
            state.visibilitiesUpdated(this, display);
            requestDismissKeyguard |= state.mRequestDismissKeyguard;
        }

        // Dismissing Keyguard happens globally using the information from all displays.
        if (requestDismissKeyguard) {
            handleDismissKeyguard();
        }
    }

调用了KeyguardController的内部静态类KeyguardDisplayState的visibilitiesUpdated方法:

    /** Represents Keyguard state per individual display. */
    private static class KeyguardDisplayState {
        private final int mDisplayId;
        private boolean mOccluded;
        ......
        void visibilitiesUpdated(KeyguardController controller, DisplayContent display) {
            final boolean lastOccluded = mOccluded;
            final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity;
            mRequestDismissKeyguard = false;
            mOccluded = false;
            mDismissingKeyguardActivity = null;

            final ActivityStack stack = getStackForControllingOccluding(display);
            if (stack != null) {
                final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
                mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
                        && stack.topRunningActivity() == topDismissing
                        && controller.canShowWhileOccluded(
                                true /* dismissKeyguard */,
                                false /* showWhenLocked */));
                if (stack.getTopDismissingKeyguardActivity() != null) {
                    mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
                }
                // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
                if (mDisplayId != DEFAULT_DISPLAY) {
                    mOccluded |= stack.canShowWithInsecureKeyguard()
                            && controller.canDismissKeyguard();
                }
            }
            // TODO(b/123372519): isShowingDream can only works on default display.
            if (mDisplayId == DEFAULT_DISPLAY) {
                mOccluded |= mService.mRootWindowContainer.getDefaultDisplay().mDisplayContent
                        .getDisplayPolicy().isShowingDreamLw();
            }

            if (lastOccluded != mOccluded) {
                controller.handleOccludedChanged(mDisplayId);
            }
            if (lastDismissActivity != mDismissingKeyguardActivity && !mOccluded
                    && mDismissingKeyguardActivity != null
                    && controller.mWindowManager.isKeyguardSecure(
                            controller.mService.getCurrentUserId())) {
                mRequestDismissKeyguard = true;
            }
        }
        ....
    }

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

PhoneWindowManager的occluded属性改变流程

然后调用了KeyguardController的handleOccludedChanged方法:

    /**
     * Called when occluded state changed.
     */
    private void handleOccludedChanged(int displayId) {
        // TODO(b/113840485): Handle app transition for individual display, and apply occluded
        // state change to secondary displays.
        // For now, only default display fully supports occluded change. Other displays only
        // updates keygaurd sleep token on that display.
        if (displayId != DEFAULT_DISPLAY) {
            updateKeyguardSleepToken(displayId);
            return;
        }

        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
        if (isKeyguardLocked()) {
            mService.deferWindowLayout();
            try {
                mRootWindowContainer.getDefaultDisplay().mDisplayContent
                        .prepareAppTransition(resolveOccludeTransit(),
                                false /* alwaysKeepCurrent */, 0 /* flags */,
                                true /* forceOverride */);
                updateKeyguardSleepToken(DEFAULT_DISPLAY);
                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
                mWindowManager.executeAppTransition();
            } finally {
                mService.continueWindowLayout();
            }
        }
        dismissDockedStackIfNeeded();
    }

调用了frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java的onKeyguardOccludedChangedLw方法:

    @Override
    public void onKeyguardOccludedChangedLw(boolean occluded) {
        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
            mPendingKeyguardOccluded = occluded;
            mKeyguardOccludedChanged = true;
        } else {
            setKeyguardOccludedLw(occluded, false /* force */);
        }
    }

如果mKeyguardDelegate.isShowing()为true时,这里只会改变mPendingKeyguardOccluded和mKeyguardOccludedChanged的值。需要在后面过渡动画时再调用setKeyguardOccludedLw方法,这个流程如下:

执行过渡动画会调用frameworks/base/services/core/java/com/android/server/wm/AppTransition.java的goodToGo方法:

    /**
     * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
     *         layout pass needs to be done
     */
    int goodToGo(int transit, ActivityRecord topOpeningApp, ArraySet<ActivityRecord> openingApps) {
        mNextAppTransition = TRANSIT_UNSET;
        mNextAppTransitionFlags = 0;
        setAppTransitionState(APP_STATE_RUNNING);
        final WindowContainer wc =
                topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
        final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;

        int redoLayout = notifyAppTransitionStartingLocked(transit,
                topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
                topOpeningAnim != null
                        ? topOpeningAnim.getStatusBarTransitionsStartTime()
                        : SystemClock.uptimeMillis(),
                AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);

        if (mRemoteAnimationController != null) {
            mRemoteAnimationController.goodToGo();
        }
        return redoLayout;
    }

调用了AppTransition的notifyAppTransitionStartingLocked方法:

    private int notifyAppTransitionStartingLocked(int transit, long duration,
            long statusBarAnimationStartTime, long statusBarAnimationDuration) {
        int redoLayout = 0;
        for (int i = 0; i < mListeners.size(); i++) {
            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration,
                    statusBarAnimationStartTime, statusBarAnimationDuration);
        }
        return redoLayout;
    }

调用了PhoneWindowManager在init方法中注册的回调onAppTransitionStartingLocked方法:

    /** {@inheritDoc} */
    @Override
    public void init(Context context, IWindowManager windowManager,
            WindowManagerFuncs windowManagerFuncs) {
        mContext = context;
        ......
        mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
            @Override
            public int onAppTransitionStartingLocked(int transit, long duration,
                    long statusBarAnimationStartTime, long statusBarAnimationDuration) {
                return handleStartTransitionForKeyguardLw(transit, duration);
            }

            @Override
            public void onAppTransitionCancelledLocked(int transit) {
                handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
            }
        });
        ......
    }

调用了PhoneWindowManager的handleStartTransitionForKeyguardLw方法:

    private int handleStartTransitionForKeyguardLw(int transit, long duration) {
        if (mKeyguardOccludedChanged) {
            if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                    + mPendingKeyguardOccluded);
            mKeyguardOccludedChanged = false;
            if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) {
                return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
            }
        }
        if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
            if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
        }
        return 0;
    }

此时mKeyguardOccludedChanged为true,因此会调用PhoneWindowManager的setKeyguardOccludedLw方法。

回到PhoneWindowManager的onKeyguardOccludedChangedLw方法中,如果mKeyguardDelegate.isShowing()为false时,这里也会调用PhoneWindowManager的setKeyguardOccludedLw方法:

    /**
     * Updates the occluded state of the Keyguard.
     *
     * @return Whether the flags have changed and we have to redo the layout.
     */
    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) {
        if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
        final boolean wasOccluded = mKeyguardOccluded;
        final boolean showing = mKeyguardDelegate.isShowing();
        final boolean changed = wasOccluded != isOccluded || force;
        if (!isOccluded && changed && showing) {
            mKeyguardOccluded = false;
            mKeyguardDelegate.setOccluded(false, true /* animate */);
            if (mKeyguardCandidate != null) {
                if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
                    mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
                }
            }
            return true;
        } else if (isOccluded && changed && showing) {
            mKeyguardOccluded = true;
            mKeyguardDelegate.setOccluded(true, false /* animate */);
            if (mKeyguardCandidate != null) {
                mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
            }
            return true;
        } else if (changed) {
            mKeyguardOccluded = isOccluded;
            mKeyguardDelegate.setOccluded(isOccluded, false /* animate */);
            return false;
        } else {
            return false;
        }
    }

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

SystemUI的occluded属性改变流程

然后调用了frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java的setOccluded方法:

    public void setOccluded(boolean isOccluded, boolean animate) {
        if (mKeyguardService != null) {
            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
            mKeyguardService.setOccluded(isOccluded, animate);
        }
        mKeyguardState.occluded = isOccluded;
    }

调用了frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java的setOccluded方法:

    @Override // Binder interface
    public void setOccluded(boolean isOccluded, boolean animate) {
        try {
            mService.setOccluded(isOccluded, animate);
        } catch (RemoteException e) {
            Slog.w(TAG , "Remote Exception", e);
        }
    }

通过binder调用了systemui进程中frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java的成员变量mBinder的setOccluded方法:

        @Override // Binder interface
        public void setOccluded(boolean isOccluded, boolean animate) {
            Trace.beginSection("KeyguardService.mBinder#setOccluded");
            checkPermission();
            mKeyguardViewMediator.setOccluded(isOccluded, animate);
            Trace.endSection();
        }

调用了frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java的setOccluded方法:

    /**
     * Notify us when the keyguard is occluded by another window
     */
    public void setOccluded(boolean isOccluded, boolean animate) {
        Trace.beginSection("KeyguardViewMediator#setOccluded");
        if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
        mHandler.removeMessages(SET_OCCLUDED);
        Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
        mHandler.sendMessage(msg);
        Trace.endSection();
    }

调用了KeyguardViewMediator的成员变量mHandler的handleMessage方法:

    /**
     * This handler will be associated with the policy thread, which will also
     * be the UI thread of the keyguard.  Since the apis of the policy, and therefore
     * this class, can be called by other threads, any action that directly
     * interacts with the keyguard ui should be posted to this handler, rather
     * than called directly.
     */
    private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ......
                case SET_OCCLUDED:
                    Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
                    handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
                    Trace.endSection();
                    break;
                ......
            }
        }
    };

调用了KeyguardViewMediator的handleSetOccluded方法:

    /**
     * Handles SET_OCCLUDED message sent by setOccluded()
     */
    private void handleSetOccluded(boolean isOccluded, boolean animate) {
        Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
        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.
                startKeyguardExitAnimation(0, 0);
            }

            if (mOccluded != isOccluded) {
                mOccluded = isOccluded;
                mUpdateMonitor.setKeyguardOccluded(isOccluded);
                mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
                        && mDeviceInteractive);
                adjustStatusBarLocked();
            }
        }
        Trace.endSection();
    }

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

总结

occluded属性的改变顺序:KeyguardController → PhoneWindowManager → SystemUI

FACE_UNLOCK_KEYGUARD_ENABLED 是 Android 系统中的一个布尔类型设置项,用于控制是否启用面容解锁功能。 如果 FACE_UNLOCK_KEYGUARD_ENABLED 设置为 true,则启用面容解锁功能;如果 FACE_UNLOCK_KEYGUARD_ENABLED 设置为 false,则禁用面容解锁功能,用户将无法使用面容解锁解锁设备。 需要注意的是,FACE_UNLOCK_KEYGUARD_ENABLED 只在 Android 10(API 级别 29)及以上版本中有效,低版本的 Android 系统中该设置项不存在。 如果你想在应用程序中控制是否启用面容解锁功能,可以使用以下代码: ```java // 获取设备管理器 DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); // 获取当前应用程序的包名 String packageName = getPackageName(); // 判断是否支持面容解锁 if (dpm.isDeviceOwnerApp(packageName)) { // 如果支持,设置 FACE_UNLOCK_KEYGUARD_ENABLED 为 true dpm.setSecureSetting(getComponentName(), Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, "1"); } else { // 如果不支持,提示用户无法设置 Toast.makeText(this, "无法设置面容解锁", Toast.LENGTH_SHORT).show(); } ``` 上述代码中,首先获取了设备管理器 DevicePolicyManager 的实例,然后获取了当前应用程序的包名。接着,判断当前应用程序是否是设备管理员,如果是,则调用 setSecureSetting(ComponentName, String, String) 方法将 FACE_UNLOCK_KEYGUARD_ENABLED 设置为 true;如果不是,则提示用户无法设置面容解锁。需要注意的是,setSecureSetting(ComponentName, String, String) 方法只在设备管理员模式下有效,如果当前应用程序不是设备管理员,则该方法无效。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SSSxCCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值