【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 = {
取消固定
课后作业:根据我上述代码流程分析思路,尝试自己进行分析取消固定应用的代码执行流程。(其实是我上班没空看.......)
浙公网安备 33010602011771号