0 %

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

2026-06-12 02:10:05

简介

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 getEnabledShortcuts(TaskView taskView, DeviceProfile deviceProfile) {

final ArrayList 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, ProtoTraceable {

// 省略部分源码

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, NavigationModeController.ModeChangedListener, Dumpable {

private final Optional> 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 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 in 渡劫指南
Copyright © 2088 幻斗之墟最新活动_仙侠MMO官网 All Rights Reserved.
友情链接