• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【Android】一种应用霸屏方式:设置固定应用

简介

Android系统中的"固定应用"功能,也常被称为"屏幕固定"或"应用固定",是一项非常实用的屏幕锁定功能。它能够将当前应用锁定在屏幕最前端,防止他人意外或故意退出该应用,从而访问您手机上的其他内容。

核心操作

  • 启用功能:在“设置-安全-固定应用”界面(不同机型路径可能不同),点击使用“固定应用功能”。
  • 固定应用:首先打开目标应用,随后进入最近任务/概览界面 (通常通过上滑悬停或点击多功能键),在对应的应用预览卡片上长按应用图标,最后在弹出的任务快捷方式(Task Shortcut)中点击“固定”按钮,完成应用固定。
  • 取消固定:在已固定的应用界面,尝试上滑并按住屏幕,或同时按住返回和最近任务按钮,系统可能会要求您输入解锁密码、图案或指纹才能完全退出。

正文

抛出问题:在“启用功能”、“固定应用”和“取消固定”时,执行的代码逻辑流程分别是什么?在应用被“固定”以后,导航栏按钮被禁用,状态栏也被禁用等,这些逻辑是怎么实现的?

下面的分析,均是基于 Android 12 的 AOSP 源码进行分析的,其它版本的 AOSP 可能有些许差异。

启用功能

在“设置-安全-固定应用”界面,有一个开关名为使用“固定应用”。定位到路径:packages/apps/Settings/src/com/android/settings/security/ScreenPinningSettings.java:

@SearchIndexable
public class ScreenPinningSettings extends SettingsPreferenceFragment
        implements OnMainSwitchChangeListener, DialogInterface.OnClickListener {
    // 省略部分源代码

    // 设置“固定应用”功能的开关状态
    private void setLockToAppEnabled(boolean isEnabled) {
        Settings.System.putInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED, isEnabled ? 1 : 0);// 设置中使用“固定应用”功能是否已经开启
        if (isEnabled) {
            // Set the value to match what we have defaulted to in the UI.
            setScreenLockUsedSetting(isScreenLockUsed());
        }
    }

    // 获取当屏幕退出“固定应用”功能时是否需要锁定设备
    private boolean isScreenLockUsed() {
        // This functionality should be kept consistent with
        // com.android.server.wm.LockTaskController (see b/127605586)
        int defaultValueIfSettingNull = mLockPatternUtils.isSecure(UserHandle.myUserId()) ? 1 : 0;
        return Settings.Secure.getInt(
                getContentResolver(),
                Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, // 屏幕固定模式退出时是否需要锁定设备
                defaultValueIfSettingNull) != 0;
    }

    // 设置屏幕固定模式退出时是否需要锁定设备
    private void setScreenLockUsedSetting(boolean isEnabled) {
        Settings.Secure.putInt(getContentResolver(), Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
                isEnabled ? 1 : 0);
    }

    @Override
    public void onSwitchChanged(Switch switchView, boolean isChecked) {
        if (isChecked) {// “使用‘固定应用’”按钮被点击打开
            // 弹出对话框,询问是否要开始固定此应用,事件监听器是 fragment 自身
            new AlertDialog.Builder(getContext())
                    .setMessage(R.string.screen_pinning_dialog_message)
                    .setPositiveButton(R.string.dlg_ok, this)
                    .setNegativeButton(R.string.dlg_cancel, this)
                    .setCancelable(false)
                    .show();
        } else {// “使用‘固定应用’”按钮被点击关闭
            setLockToAppEnabled(false);//关闭“固定应用”功能
            updateDisplay();
        }
    }

    @Override
    public void onClick(DialogInterface dialogInterface, int which) {
        if (which == DialogInterface.BUTTON_POSITIVE) {// 以 fragment 自身为监听器,监听到对话框点击确认按钮
            setLockToAppEnabled(true);//启用“固定应用”功能
        } else {
            mSwitchBar.setChecked(false);
        }
        updateDisplay();
    }
}

固定应用

本文主要讨论的是“固定应用”功能,打开应用以及进入最近任务/概览界面的逻辑流程不在此分析。我们从点击任务快捷方式(Task Shortcut)中的“固定”按钮开始分析:

首先定位到 packages/apps/Launcher3/quickstep/src/com/android/quickstep/TaskOverlayFactory.java

public class TaskOverlayFactory implements ResourceBasedOverride {
    public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView, DeviceProfile deviceProfile) {
        final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
        for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
            // 省略部分代码
        }
        // 省略部分代码
        return shortcuts;
    }

    /** Note that these will be shown in order from top to bottom, if available for the task. */
    private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
            TaskShortcutFactory.APP_INFO,
            TaskShortcutFactory.SPLIT_SCREEN,
            TaskShortcutFactory.PIN,
            TaskShortcutFactory.INSTALL,
            TaskShortcutFactory.FREE_FORM,
            TaskShortcutFactory.WELLBEING
    };
}

由上述代码,可知“任务快捷方式(Task Shortcut)”的类型是 \(TaskShortcutFactory\),于是定位到/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TaskShortcutFactory.java:

public interface TaskShortcutFactory {
    TaskShortcutFactory PIN = (activity, tv) -> {
        if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
            return null;
        }
        if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {// 判断“固定应用”功能是否已开启
            return null;
        }
        if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {// 当前已经有应用被固定
            // We shouldn't be able to pin while an app is locked.
            return null;
        }
        return new PinSystemShortcut(activity, tv);
    };

    class PinSystemShortcut extends SystemShortcut {

        private static final String TAG = "PinSystemShortcut";

        private final TaskView mTaskView;

        public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) {
            super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo());
            mTaskView = tv;
        }

        @Override
        public void onClick(View view) {// 任务快捷方式的点击事件在这里实现
            if (mTaskView.launchTaskAnimated() != null) {
                SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(mTaskView.getTask().key.id);// 关键代码:Launcher3 使用 SystemUI 的代理对象进行应用的固定
            }
            dismissTaskMenuView(mTarget);
            mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
                    .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
        }
    }
}

根据上述代码,可以大致推测出想要进行“固定应用”需要先在设置中打开“固定应用功能”的原因在 \(ActivityManagerWrapper.java\) 中。定位到frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java:

public class ActivityManagerWrapper {
    /**
    * @return whether screen pinning is enabled.
    */
    public boolean isScreenPinningEnabled() {
        final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver();
        return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0;// 返回设置中使用“固定应用”功能是否已经开启
    }

    /**
    * @return whether there is currently a locked task (ie. in screen pinning).
    */
    public boolean isLockToAppActive() {
        try {
            return ActivityTaskManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE;// 状态压缩,根据掩码位判断是否当前应用已经“固定”
        } catch (RemoteException e) {
            return false;
        }
    }
}

能否顺利执行到 \(SystemUiProxy\) 去的前置条件已经分析完了,接下来分析 \(Launcher3\) 是如何使用 \(SystemUI\) 的代理对象进行应用的固定的。定位到/packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java:

import com.android.systemui.shared.recents.ISystemUiProxy;

public class SystemUiProxy implements ISystemUiProxy,
        SysUINavigationMode.NavigationModeChangeListener {
    private ISystemUiProxy mSystemUiProxy;// SystemUI 代理对象,即 Launcher3 与 SystemUI 进行 IPC 的对象
    
    // 省略部分源代码

    public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
            IOneHanded oneHanded, IShellTransitions shellTransitions,
            IStartingWindow startingWindow,
            ISmartspaceTransitionController smartSpaceTransitionController) {
        unlinkToDeath();
        mSystemUiProxy = proxy;// mSystemUiProxy唯一赋值的位置,追踪代码的突破口
        // 省略部分源代码
    }

    @Override
    public void startScreenPinning(int taskId) {
        if (mSystemUiProxy != null) {
            try {
                mSystemUiProxy.startScreenPinning(taskId);// 使用 SystemUI 的代理对象,传入 taskId,执行“固定应用”的逻辑
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call startScreenPinning", e);
            }
        }
    }

继续追踪到 /frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl:

interface ISystemUiProxy {
   
    /**
     * Begins screen pinning on the provided {@param taskId}.
     */
    void startScreenPinning(int taskId) = 1;

    // 省略其他源代码
}

这是个 aidl 文件,由此可知 \(Launcher3\) 与 \(SystemUI\) 通信是跨进程通信。由 \(SystemUiProxy\) 源码分析,可知追踪代码的突破口是 \(mSystemUiProxy\) 的赋值,即需要追踪 \(SystemUiProxy\) 的 setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen, IOneHanded oneHanded, IShellTransitions shellTransitions, IStartingWindow startingWindow, ISmartspaceTransitionController smartSpaceTransitionController) 方法的调用。
此处还有个搜索代码的小技巧,凡是用到 aidl 进行 IPC 的,都可以用 "aidl接口名.Stub" 为关键字进行代码搜索。
定位到 packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java:

public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>, ProtoTraceable<LauncherTraceProto.Builder> {
    // 省略部分源码
    public class TISBinder extends IOverviewProxy.Stub {
        @BinderThread
        public void onInitialize(Bundle bundle) {
            ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));// 获取远程服务的代理对象
            IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
            ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN));
            IOneHanded onehanded = IOneHanded.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
            IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
            IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
            ISmartspaceTransitionController smartspaceTransitionController = ISmartspaceTransitionController.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER));
            MAIN_EXECUTOR.execute(() -> {

                // 核心代码:获取到 SystemUiProxy 的单例,调用 setProxy 方法对 mSystemUiProxy 传入参数 proxy
                SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip, splitscreen, onehanded, shellTransitions, startingWindow, smartspaceTransitionController);

                TouchInteractionService.this.initInputMonitor();
                preloadOverview(true /* fromInit */);
                mDeviceState.runOnUserUnlocked(() -> {
                    final BaseActivityInterface ai = mOverviewComponentObserver.getActivityInterface();
                    if (ai == null) return;
                    ai.onOverviewServiceBound();
                });
            });
            sIsInitialized = true;
        }
    }
}

既然已经知道了这部分的逻辑是 \(Launcher3\) 与 \(SystemUI\) 进行 IPC,那么 \(ISystemUiProxy\) 的服务端代码大概率也会是在 \(SystemUI\) 中实现,经过代码搜索,可以定位到路径 frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java:

@SysUISingleton
public class OverviewProxyService extends CurrentUserTracker implements CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener, Dumpable {
    private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
    @VisibleForTesting
    public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
        @Override
        public void startScreenPinning(int taskId) {// 执行固定应用
            if (!verifyCaller("startScreenPinning")) {
                return;
            }
            final long token = Binder.clearCallingIdentity();
            try {
                mHandler.post(() -> {
                    mStatusBarOptionalLazy.ifPresent(
                            statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
                                    false /* allowCancel */));
                });
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override
        public void stopScreenPinning() {// 取消固定应用
            if (!verifyCaller("stopScreenPinning")) {
                return;
            }
            final long token = Binder.clearCallingIdentity();
            try {
                mHandler.post(() -> {
                    try {
                        ActivityTaskManager.getService().stopSystemLockTaskMode();
                    } catch (RemoteException e) {
                        Log.e(TAG_OPS, "Failed to stop screen pinning");
                    }
                });
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
        // 省略部分源代码
    }
    // 省略部分源代码
}

然后定位到路径 frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java:

private final ScreenPinningRequest mScreenPinningRequest;

@Override
public void showScreenPinningRequest(int taskId) {
    if (mKeyguardStateController.isShowing()) {
        // Don't allow apps to trigger this from keyguard.
        return;
    }
    // Show screen pinning request, since this comes from an app, show 'no thanks', button.
    showScreenPinningRequest(taskId, true);
}

public void showScreenPinningRequest(int taskId, boolean allowCancel) {
    mScreenPinningRequest.showPrompt(taskId, allowCancel);
}

继续追溯到 frameworks/base/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java。在 \(ScreenPinningRequest.java\) 中,有个名为 \(RequestWindowView\) 的内部类是“固定应用”功能的关键。内部类 \(RequestWindowView\) 是个弹出的对话框界面(本质是帧布局 \(FrameLayout\)),且以 \(ScreenPinningRequest.this\) 直接作为点击事件监听器,在点击对话框的确认按钮后触发容器本身的点击事件的回调。核心代码逻辑如下:

public class ScreenPinningRequest implements View.OnClickListener, NavigationModeController.ModeChangedListener {
    // 省略部分源码
    public void clearPrompt() {
        if (mRequestWindow != null) {
            mWindowManager.removeView(mRequestWindow);
            mRequestWindow = null;
        }
    }

    public void showPrompt(int taskId, boolean allowCancel) {
        try {
            clearPrompt();
        } catch (IllegalArgumentException e) {
            // If the call to show the prompt fails due to the request window not already being
            // attached, then just ignore the error since we will be re-adding it below.
        }

        this.taskId = taskId;

        // 核心代码:RequestWindowView 是一个内部类,将 ScreenPinningRequest 直接作为点击事件监听器
        mRequestWindow = new RequestWindowView(mContext, allowCancel);

        mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

        // show the confirmation
        WindowManager.LayoutParams lp = getWindowLayoutParams();
        mWindowManager.addView(mRequestWindow, lp);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
            try {
                ActivityTaskManager.getService().startSystemLockTaskMode(taskId);// mRequestWindow 的确认按钮触发的点击事件
            } catch (RemoteException e) {}
        }
        clearPrompt();
    }

    // 省略核心内部类 RequestWindowView 的代码(因为代码很长),但是必须知道这个类的存在及其功能
}

由 \(ScreenPinningRequest.java\) 的点击事件回调方法 \(onClick(View v)\),可以得知 \(SystemUI\) 会与 \(ActivityTaskManagerService\) 再进行跨进程通信,定位到frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java:

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    // 省略部分代码
    LockTaskController getLockTaskController() {
        return mLockTaskController;
    }

    @Override
    public void startSystemLockTaskMode(int taskId) {// 对指定的 taskId 进行应用固定
        enforceTaskPermission("startSystemLockTaskMode");
        // This makes inner call to look as if it was initiated by system.
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                final Task task = mRootWindowContainer.anyTaskForId(taskId,
                        MATCH_ATTACHED_TASK_ONLY);
                if (task == null) {
                    return;
                }

                // When starting lock task mode the root task must be in front and focused
                task.getRootTask().moveToFront("startSystemLockTaskMode");
                startLockTaskMode(task, true /* isSystemCaller */);// 关键逻辑
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    /**
     * This API should be called by SystemUI only when user perform certain action to dismiss
     * lock task mode. We should only dismiss pinned lock task mode in this case.
     */
    @Override
    public void stopSystemLockTaskMode() throws RemoteException {// 取消固定应用
        enforceTaskPermission("stopSystemLockTaskMode");
        stopLockTaskModeInternal(null, true /* isSystemCaller */);
    }

    void startLockTaskMode(@Nullable Task task, boolean isSystemCaller) {
        ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskMode: %s", task);
        if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
            return;
        }

        final Task rootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
        if (rootTask == null || task != rootTask.getTopMostTask()) {
            throw new IllegalArgumentException("Invalid task, not in foreground");
        }

        // {@code isSystemCaller} is used to distinguish whether this request is initiated by the
        // system or a specific app.
        // * System-initiated requests will only start the pinned mode (screen pinning)
        // * App-initiated requests
        //   - will put the device in fully locked mode (LockTask), if the app is allowlisted
        //   - will start the pinned mode, otherwise
        final int callingUid = Binder.getCallingUid();
        final long ident = Binder.clearCallingIdentity();
        try {
            // When a task is locked, dismiss the root pinned task if it exists
            mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);

            getLockTaskController().startLockTaskMode(task, isSystemCaller, callingUid);// 关键代码逻辑
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    void stopLockTaskModeInternal(@Nullable IBinder token, boolean isSystemCaller) {
        final int callingUid = Binder.getCallingUid();
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                Task task = null;
                if (token != null) {
                    final ActivityRecord r = ActivityRecord.forTokenLocked(token);
                    if (r == null) {
                        return;
                    }
                    task = r.getTask();
                }
                getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
            }
            // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
            // task and jumping straight into a call in the case of emergency call back.
            TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
            if (tm != null) {
                tm.showInCallScreen(false);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}

接下来进一步定位到 frameworks/base/services/core/java/com/android/server/wm/LockTaskController.java:

public class LockTaskController {
    /**
     * Method to start lock task mode on a given task.
     *
     * @param task the task that should be locked.
     * @param isSystemCaller indicates whether this request was initiated by the system via
     *                       {@link ActivityTaskManagerService#startSystemLockTaskMode(int)}. If
     *                       {@code true}, this intends to start pinned mode; otherwise, we look
     *                       at the calling task's mLockTaskAuth to decide which mode to start.
     * @param callingUid the caller that requested the launch of lock task mode.
     */
    void startLockTaskMode(@NonNull Task task, boolean isSystemCaller, int callingUid) {
        if (!isSystemCaller) {// 非系统调用者
            task.mLockTaskUid = callingUid;
            if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
                // startLockTask() called by app, but app is not part of lock task allowlist. Show
                // app pinning request. We will come back here with isSystemCaller true.
                ProtoLog.w(WM_DEBUG_LOCKTASK, "Mode default, asking user");
                StatusBarManagerInternal statusBarManager = LocalServices.getService(StatusBarManagerInternal.class);// 获取远程服务
                if (statusBarManager != null) {
                    statusBarManager.showScreenPinningRequest(task.mTaskId);// framework 与 StatusBar 所在进程进行 IPC
                }
                return;
            }
        }

        // System can only initiate screen pinning, not full lock task mode
        ProtoLog.w(WM_DEBUG_LOCKTASK, "%s", isSystemCaller ? "Locking pinned" : "Locking fully");
        setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED, "startLockTask", true);// 正常系统流程是走这里
    }

    /**
     * Start lock task mode on the given task.
     * @param lockTaskModeState whether fully locked or pinned mode.
     * @param andResume whether the task should be brought to foreground as part of the operation.
     */
    private void setLockTaskMode(@NonNull Task task, int lockTaskModeState,
                                 String reason, boolean andResume) {
        // Should have already been checked, but do it again.
        if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
            ProtoLog.w(WM_DEBUG_LOCKTASK,
                    "setLockTaskMode: Can't lock due to auth");
            return;
        }
        if (isLockTaskModeViolation(task)) {
            Slog.e(TAG_LOCKTASK, "setLockTaskMode: Attempt to start an unauthorized lock task.");
            return;
        }

        final Intent taskIntent = task.intent;
        if (mLockTaskModeTasks.isEmpty() && taskIntent != null) {
            mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.mUserId);
            // Start lock task on the handler thread
            mHandler.post(() -> performStartLockTask(
                    taskIntent.getComponent().getPackageName(),
                    task.mUserId,
                    lockTaskModeState));
        }
        ProtoLog.w(WM_DEBUG_LOCKTASK, "setLockTaskMode: Locking to %s Callers=%s",
                task, Debug.getCallers(4));

        if (!mLockTaskModeTasks.contains(task)) {
            mLockTaskModeTasks.add(task);
        }

        if (task.mLockTaskUid == -1) {
            task.mLockTaskUid = task.effectiveUid;
        }

        if (andResume) {
            mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
                    lockTaskModeState != LOCK_TASK_MODE_NONE);
            mSupervisor.mRootWindowContainer.resumeFocusedTasksTopActivities();
            final Task rootTask = task.getRootTask();
            if (rootTask != null) {
                rootTask.mDisplayContent.executeAppTransition();
            }
        } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
            mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
                    mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea(),
                    task.getRootTask(), true /* forceNonResizable */);
        }
    }

    // This method should only be called on the handler thread
    private void performStartLockTask(String packageName, int userId, int lockTaskModeState) {
        // When lock task starts, we disable the status bars.
        try {
            if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
                getStatusBarService().showPinningEnterExitToast(true /* entering */);
            }
            mWindowManager.onLockTaskStateChanged(lockTaskModeState);
            mLockTaskModeState = lockTaskModeState;
            mTaskChangeNotificationController.notifyLockTaskModeChanged(mLockTaskModeState);
            setStatusBarState(lockTaskModeState, userId);// 设置状态栏的状态
            setKeyguardState(lockTaskModeState, userId);
            if (getDevicePolicyManager() != null) {
                getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId);
            }
        } catch (RemoteException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Helper method for configuring the status bar disabled state.
     * Should only be called on the handler thread to avoid race.
     */
    private void setStatusBarState(int lockTaskModeState, int userId) {
        //ifdef VENDOR_UROVO weiyu add on 2020-10-26 [s]
        String isShowStatusBar = Settings.System.getString(mContext.getContentResolver(), "SHOW_STATUSBAR_LOCKTASKMODE");
        if (isShowStatusBar != null && isShowStatusBar.equals("true")) {
            lockTaskModeState = LOCK_TASK_MODE_NONE;
        }
        // urovo weiyu add on 2020-10-26 [e]
        IStatusBarService statusBar = getStatusBarService();
        if (statusBar == null) {
            Slog.e(TAG, "Can't find StatusBarService");
            return;
        }

        // Default state, when lockTaskModeState == LOCK_TASK_MODE_NONE
        int flags1 = StatusBarManager.DISABLE_NONE;
        int flags2 = StatusBarManager.DISABLE2_NONE;

        if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
            flags1 = STATUS_BAR_MASK_PINNED;

        } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
            int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
            Pair<Integer, Integer> statusBarFlags = getStatusBarDisableFlags(lockTaskFeatures);
            flags1 = statusBarFlags.first;
            flags2 = statusBarFlags.second;
        }

        try {
            statusBar.disable(flags1, mToken, mContext.getPackageName());//根据掩码关闭一些功能
            statusBar.disable2(flags2, mToken, mContext.getPackageName());//根据掩码关闭一些功能
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to set status bar flags", e);
        }
    }

    // Should only be called on the handler thread
    @Nullable
    private IStatusBarService getStatusBarService() {
        if (mStatusBarService == null) {
            mStatusBarService = IStatusBarService.Stub.asInterface(
                    ServiceManager.checkService(STATUS_BAR_SERVICE));
            if (mStatusBarService == null) {
                Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE");
            }
        }
        return mStatusBarService;
    }
}

再进一步定位到frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java:

public interface StatusBarManagerInternal {
    // 省略部分源代码
    void showScreenPinningRequest(int taskId);
}

随后定位到实现接口 \(StatusBarManagerInternal.java\) 的远程服务类 \(StatusBarManagerService.java\),定位到路径frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java:

public class StatusBarManagerService extends IStatusBarService.Stub implements DisplayListener {
    private volatile IStatusBar mBar;

    /**
     * Private API used by NotificationManagerService.
     */
    private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
        // 省略部分源码
        @Override
        public void showScreenPinningRequest(int taskId) {
            if (mBar != null) {
                try {
                    mBar.showScreenPinningRequest(taskId);
                } catch (RemoteException e) {
                }
            }
        }
    }
}

AOSP 的 \(StatusBarManager\),定义了一组用于禁用状态类不同功能的标志位。这些变量的含义如下:

变量名称 变量含义
DISABLE_MASK_DEFAULT 组合掩码,包含了多个禁用标志的按位或运算结果
DISABLE_EXPAND 禁用状态栏的下拉展开功能,用户无法通过下拉手势打开通知面板
DISABLE_NOTIFICATION_ICONS 隐藏状态栏中的通知图标,新通知不会在状态栏显示图标
DISABLE_NOTIFICATION_ALERTS 禁用通知提醒,包括声音、震动等通知反馈
DISABLE_NOTIFICATION_TICKER 禁用通知滚动文本(ticker text),新通知不会在状态栏显示滚动文字
DISABLE_SYSTEM_INFO 隐藏系统信息显示,可能包括电池电量、信号强度等系统状态图标
DISABLE_RECENT 禁用最近任务键功能,用户无法通过最近任务键查看最近使用的应用
DISABLE_HOME 禁用主页键功能,用户无法通过主页键返回主屏幕
DISABLE_BACK 禁用返回键功能,用户无法通过返回键返回上一界面
DISABLE_CLOCK 隐藏状态栏中的时钟显示
DISABLE_SEARCH 禁用搜索键功能(如果设备有搜索键)
DISABLE_ONGOING_CALL_CHIP 禁用正在进行通话的提示芯片,通话过程中不会在状态栏显示通话状态指示

若想控制 \(StatusBar\) 的相关功能(例如状态栏的图标的显示),则由 \(LockTaskController.java\) 的 \(setStatusBarState(int lockTaskModeState, int userId)\) 代码逻辑,可以知道功能是通过掩码进行控制的,定位到frameworks/base/core/java/android/app/StatusBarManager.java,在进行“固定应用”时不想隐藏状态栏图标的话,可以合入以下 patch:

diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 77bcef3ae009..74dc402fed9b 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -31,6 +31,7 @@ import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.View;
@@ -85,10 +86,20 @@ public class StatusBarManager {
     public static final int DISABLE_NONE = 0x00000000;
 
     /** @hide */
-    public static final int DISABLE_MASK = DISABLE_EXPAND | DISABLE_NOTIFICATION_ICONS
+    private static final int DISABLE_MASK_DEFAULT = DISABLE_EXPAND | DISABLE_NOTIFICATION_ICONS
             | DISABLE_NOTIFICATION_ALERTS | DISABLE_NOTIFICATION_TICKER
             | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK
             | DISABLE_SEARCH | DISABLE_ONGOING_CALL_CHIP;
+    /** @hide */
+    private static final int DISABLE_MASK_CUSTOMIZE = DISABLE_EXPAND
+            | DISABLE_NOTIFICATION_ALERTS | DISABLE_NOTIFICATION_TICKER
+            | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK
+            | DISABLE_SEARCH | DISABLE_ONGOING_CALL_CHIP;
+    
+    /** @hide */
+    public static final int DISABLE_MASK = SystemProperties.getBoolean("persist.sys.urv.statusbar.disable.mask.customize", false) 
+            ? DISABLE_MASK_CUSTOMIZE
+            : DISABLE_MASK_DEFAULT;
 
     /** @hide */
     @IntDef(flag = true, prefix = {"DISABLE_"}, value = {

取消固定

课后作业:根据我上述代码流程分析思路,尝试自己进行分析取消固定应用的代码执行流程。(其实是我上班没空看.......)

posted on 2025-11-05 14:43  RomanLin  阅读(0)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3