/* * Copyright 2017, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.content.Context.STATUS_BAR_SERVICE; import static android.content.Intent.ACTION_CALL_EMERGENCY; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_CURRENT; import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.telecom.TelecomManager; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * Helper class that deals with all things related to task locking. This includes the screen pinning * mode that can be launched via System UI as well as the fully locked mode that can be achieved * on fully managed devices. * * Note: All methods in this class should only be called with the ActivityManagerService lock held. * * @see Activity#startLockTask() * @see Activity#stopLockTask() */ public class LockTaskController { private static final String TAG = TAG_WITH_CLASS_NAME ? "LockTaskController" : TAG_AM; private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; @VisibleForTesting static final int STATUS_BAR_MASK_LOCKED = StatusBarManager.DISABLE_MASK & (~StatusBarManager.DISABLE_EXPAND) & (~StatusBarManager.DISABLE_NOTIFICATION_TICKER) & (~StatusBarManager.DISABLE_SYSTEM_INFO) & (~StatusBarManager.DISABLE_BACK); @VisibleForTesting static final int STATUS_BAR_MASK_PINNED = StatusBarManager.DISABLE_MASK & (~StatusBarManager.DISABLE_BACK) & (~StatusBarManager.DISABLE_HOME) & (~StatusBarManager.DISABLE_RECENT); private static final SparseArray<Pair<Integer, Integer>> STATUS_BAR_FLAG_MAP_LOCKED; static { STATUS_BAR_FLAG_MAP_LOCKED = new SparseArray<>(); STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO, new Pair<>(StatusBarManager.DISABLE_CLOCK, StatusBarManager.DISABLE2_SYSTEM_ICONS)); STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS, new Pair<>(StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_ALERTS, StatusBarManager.DISABLE2_NOTIFICATION_SHADE)); STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME, new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE)); STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW, new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE)); STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS, new Pair<>(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_GLOBAL_ACTIONS)); } /** Tag used for disabling of keyguard */ private static final String LOCK_TASK_TAG = "Lock-to-App"; private final IBinder mToken = new Binder(); private final ActivityStackSupervisor mSupervisor; private final Context mContext; // The following system services cannot be final, because they do not exist when this class // is instantiated during device boot @VisibleForTesting IStatusBarService mStatusBarService; @VisibleForTesting IDevicePolicyManager mDevicePolicyManager; @VisibleForTesting WindowManagerService mWindowManager; @VisibleForTesting LockPatternUtils mLockPatternUtils; @VisibleForTesting TelecomManager mTelecomManager; /** * The chain of tasks in LockTask mode, in the order of when they first entered LockTask mode. * * The first task in the list, which started the current LockTask session, is called the root * task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are * more than one locked tasks, the root task can't be finished. Nor can it be moved to the back * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)}; * * Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in * this list, and the device will exit LockTask mode. * * The list is empty if LockTask is inactive. */ private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>(); /** * Packages that are allowed to be launched into the lock task mode for each user. */ private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>(); /** * Features that are allowed by DPC to show during LockTask mode. */ private final SparseIntArray mLockTaskFeatures = new SparseIntArray(); /** * Store the current lock task mode. Possible values: * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, * {@link ActivityManager#LOCK_TASK_MODE_PINNED} */ private int mLockTaskModeState = LOCK_TASK_MODE_NONE; /** * This is ActivityStackSupervisor's Handler. */ private final Handler mHandler; LockTaskController(Context context, ActivityStackSupervisor supervisor, Handler handler) { mContext = context; mSupervisor = supervisor; mHandler = handler; } /** * Set the window manager instance used in this class. This is necessary, because the window * manager does not exist during instantiation of this class. */ void setWindowManager(WindowManagerService windowManager) { mWindowManager = windowManager; } /** * @return the current lock task state. This can be any of * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, * {@link ActivityManager#LOCK_TASK_MODE_PINNED}. */ int getLockTaskModeState() { return mLockTaskModeState; } /** * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the * back of the stack. */ @VisibleForTesting boolean isTaskLocked(TaskRecord task) { return mLockTaskModeTasks.contains(task); } /** * @return {@code true} whether this task first started the current LockTask session. */ private boolean isRootTask(TaskRecord task) { return mLockTaskModeTasks.indexOf(task) == 0; } /** * @return whether the given activity is blocked from finishing, because it is the only activity * of the last locked task and finishing it would mean that lock task mode is ended illegally. */ boolean activityBlockedFromFinish(ActivityRecord activity) { final TaskRecord task = activity.getTask(); if (activity == task.getRootActivity() && activity == task.getTopActivity() && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV && isRootTask(task)) { Slog.i(TAG, "Not finishing task in lock task mode"); showLockTaskToast(); return true; } return false; } /** * @return whether the given task can be moved to the back of the stack with * {@link ActivityStack#moveTaskToBackLocked(int)} * @see #mLockTaskModeTasks */ boolean canMoveTaskToBack(TaskRecord task) { if (isRootTask(task)) { showLockTaskToast(); return false; } return true; } /** * @return whether the requested task is allowed to be locked (either whitelisted, or declares * lockTaskMode="always" in the manifest). */ boolean isTaskWhitelisted(TaskRecord task) { switch(task.mLockTaskAuth) { case LOCK_TASK_AUTH_WHITELISTED: case LOCK_TASK_AUTH_LAUNCHABLE: case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: return true; case LOCK_TASK_AUTH_PINNABLE: case LOCK_TASK_AUTH_DONT_LOCK: default: return false; } } /** * @return whether the requested task is disallowed to be launched. */ boolean isLockTaskModeViolation(TaskRecord task) { return isLockTaskModeViolation(task, false); } /** * @param isNewClearTask whether the task would be cleared as part of the operation. * @return whether the requested task is disallowed to be launched. */ boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) { if (isLockTaskModeViolationInternal(task, isNewClearTask)) { showLockTaskToast(); return true; } return false; } /** * @return the root task of the lock task. */ TaskRecord getRootTask() { if (mLockTaskModeTasks.isEmpty()) { return null; } return mLockTaskModeTasks.get(0); } private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) { // TODO: Double check what's going on here. If the task is already in lock task mode, it's // likely whitelisted, so will return false below. if (isTaskLocked(task) && !isNewClearTask) { // If the task is already at the top and won't be cleared, then allow the operation return false; } // Allow recents activity if enabled by policy if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) { return false; } // Allow emergency calling when the device is protected by a locked keyguard if (isKeyguardAllowed(task.userId) && isEmergencyCallTask(task)) { return false; } return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty()); } private boolean isRecentsAllowed(int userId) { return (getLockTaskFeaturesForUser(userId) & DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW) != 0; } private boolean isKeyguardAllowed(int userId) { return (getLockTaskFeaturesForUser(userId) & DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0; } private boolean isEmergencyCallTask(TaskRecord task) { final Intent intent = task.intent; if (intent == null) { return false; } // 1. The emergency keypad activity launched on top of the keyguard if (EMERGENCY_DIALER_COMPONENT.equals(intent.getComponent())) { return true; } // 2. The intent sent by the keypad, which is handled by Telephony if (ACTION_CALL_EMERGENCY.equals(intent.getAction())) { return true; } // 3. Telephony then starts the default package for making the call final TelecomManager tm = getTelecomManager(); final String dialerPackage = tm != null ? tm.getSystemDialerPackage() : null; if (dialerPackage != null && dialerPackage.equals(intent.getComponent().getPackageName())) { return true; } return false; } /** * Stop the current lock task mode. * * This is called by {@link ActivityManagerService} and performs various checks before actually * finishing the locked task. * * @param task the task that requested the end of lock task mode ({@code null} for quitting app * pinning mode) * @param isSystemCaller indicates whether this request comes from the system via * {@link ActivityManagerService#stopSystemLockTaskMode()}. If * {@code true}, it means the user intends to stop pinned mode through UI; * otherwise, it's called by an app and we need to stop locked or pinned * mode, subject to checks. * @param callingUid the caller that requested the end of lock task mode. * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in * foreground) * @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if * they differ from the one that launched lock task mode. */ void stopLockTaskMode(@Nullable TaskRecord task, boolean isSystemCaller, int callingUid) { if (mLockTaskModeState == LOCK_TASK_MODE_NONE) { return; } if (isSystemCaller) { if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { clearLockedTasks("stopAppPinning"); } else { Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true"); showLockTaskToast(); } } else { // Ensure calling activity is not null if (task == null) { throw new IllegalArgumentException("can't stop LockTask for null task"); } // Ensure the same caller for startLockTaskMode and stopLockTaskMode. // It is possible lockTaskMode was started by the system process because // android:lockTaskMode is set to a locking value in the application manifest // instead of the app calling startLockTaskMode. In this case // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the // {@link TaskRecord.effectiveUid} instead. Also caller with // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task. if (callingUid != task.mLockTaskUid && (task.mLockTaskUid != 0 || callingUid != task.effectiveUid)) { throw new SecurityException("Invalid uid, expected " + task.mLockTaskUid + " callingUid=" + callingUid + " effectiveUid=" + task.effectiveUid); } // We don't care if it's pinned or locked mode; this will stop it anyways. clearLockedTask(task); } } /** * Clear all locked tasks and request the end of LockTask mode. * * This method is called by {@link UserController} when starting a new foreground user, and, * unlike {@link #stopLockTaskMode(TaskRecord, boolean, int)}, it doesn't perform the checks. */ void clearLockedTasks(String reason) { if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason); if (!mLockTaskModeTasks.isEmpty()) { clearLockedTask(mLockTaskModeTasks.get(0)); } } /** * Clear one locked task from LockTask mode. * * If the requested task is the root task (see {@link #mLockTaskModeTasks}), then all locked * tasks are cleared. Otherwise, only the requested task is cleared. LockTask mode is stopped * when the last locked task is cleared. * * @param task the task to be cleared from LockTask mode. */ void clearLockedTask(final TaskRecord task) { if (task == null || mLockTaskModeTasks.isEmpty()) return; if (task == mLockTaskModeTasks.get(0)) { // We're removing the root task while there are other locked tasks. Therefore we should // clear all locked tasks in reverse order. for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx > 0; --taskNdx) { clearLockedTask(mLockTaskModeTasks.get(taskNdx)); } } removeLockedTask(task); if (mLockTaskModeTasks.isEmpty()) { return; } task.performClearTaskLocked(); mSupervisor.resumeFocusedStackTopActivityLocked(); } /** * Remove the given task from the locked task list. If this was the last task in the list, * lock task mode is stopped. */ private void removeLockedTask(final TaskRecord task) { if (!mLockTaskModeTasks.remove(task)) { return; } if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task); if (mLockTaskModeTasks.isEmpty()) { if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task + " last task, reverting locktask mode. Callers=" + Debug.getCallers(3)); mHandler.post(() -> performStopLockTask(task.userId)); } } // This method should only be called on the handler thread private void performStopLockTask(int userId) { // When lock task ends, we enable the status bars. try { setStatusBarState(LOCK_TASK_MODE_NONE, userId); setKeyguardState(LOCK_TASK_MODE_NONE, userId); if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { lockKeyguardIfNeeded(); } if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); } if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { getStatusBarService().showPinningEnterExitToast(false /* entering */); } mWindowManager.onLockTaskStateChanged(LOCK_TASK_MODE_NONE); } catch (RemoteException ex) { throw new RuntimeException(ex); } finally { mLockTaskModeState = LOCK_TASK_MODE_NONE; } } /** * Show the lock task violation toast. Currently we only show toast for screen pinning mode, and * no-op if the device is in locked mode. */ void showLockTaskToast() { if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { try { getStatusBarService().showPinningEscapeToast(); } catch (RemoteException e) { Slog.e(TAG, "Failed to send pinning escape toast", e); } } } // Starting lock task /** * 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 ActivityManagerService#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 TaskRecord 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 whitelist. Show // app pinning request. We will come back here with isSystemCaller true. if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user"); StatusBarManagerInternal statusBarManager = LocalServices.getService( StatusBarManagerInternal.class); if (statusBarManager != null) { statusBarManager.showScreenPinningRequest(task.taskId); } return; } } // System can only initiate screen pinning, not full lock task mode if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, 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 TaskRecord task, int lockTaskModeState, String reason, boolean andResume) { // Should have already been checked, but do it again. if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { if (DEBUG_LOCKTASK) Slog.w(TAG_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.userId); // Start lock task on the handler thread mHandler.post(() -> performStartLockTask( taskIntent.getComponent().getPackageName(), task.userId, lockTaskModeState)); } if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task + " Callers=" + 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.resumeFocusedStackTopActivityLocked(); mWindowManager.executeAppTransition(); } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, task.getStack(), 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; setStatusBarState(lockTaskModeState, userId); setKeyguardState(lockTaskModeState, userId); if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId); } } catch (RemoteException ex) { throw new RuntimeException(ex); } } /** * Update packages that are allowed to be launched in lock task mode. * @param userId Which user this whitelist is associated with * @param packages The whitelist of packages allowed in lock task mode * @see #mLockTaskPackages */ void updateLockTaskPackages(int userId, String[] packages) { mLockTaskPackages.put(userId, packages); boolean taskChanged = false; for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx); final boolean wasWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED; lockedTask.setLockTaskAuth(); final boolean isWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED; if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED || lockedTask.userId != userId || !wasWhitelisted || isWhitelisted) { continue; } // Terminate locked tasks that have recently lost whitelist authorization. if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " + lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString()); removeLockedTask(lockedTask); lockedTask.performClearTaskLocked(); taskChanged = true; } for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) { mSupervisor.getChildAt(displayNdx).onLockTaskPackagesUpdated(); } final ActivityRecord r = mSupervisor.topRunningActivityLocked(); final TaskRecord task = (r != null) ? r.getTask() : null; if (mLockTaskModeTasks.isEmpty() && task!= null && task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) { // This task must have just been authorized. if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: starting new locktask task=" + task); setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false); taskChanged = true; } if (taskChanged) { mSupervisor.resumeFocusedStackTopActivityLocked(); } } boolean isPackageWhitelisted(int userId, String pkg) { if (pkg == null) { return false; } String[] whitelist; whitelist = mLockTaskPackages.get(userId); if (whitelist == null) { return false; } for (String whitelistedPkg : whitelist) { if (pkg.equals(whitelistedPkg)) { return true; } } return false; } /** * Update the UI features that are enabled for LockTask mode. * @param userId Which user these feature flags are associated with * @param flags Bitfield of feature flags * @see DevicePolicyManager#setLockTaskFeatures(ComponentName, int) */ void updateLockTaskFeatures(int userId, int flags) { int oldFlags = getLockTaskFeaturesForUser(userId); if (flags == oldFlags) { return; } mLockTaskFeatures.put(userId, flags); if (!mLockTaskModeTasks.isEmpty() && userId == mLockTaskModeTasks.get(0).userId) { mHandler.post(() -> { if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { setStatusBarState(mLockTaskModeState, userId); setKeyguardState(mLockTaskModeState, userId); } }); } } /** * 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) { 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); } } /** * Helper method for configuring the keyguard disabled state. * Should only be called on the handler thread to avoid race. */ private void setKeyguardState(int lockTaskModeState, int userId) { if (lockTaskModeState == LOCK_TASK_MODE_NONE) { mWindowManager.reenableKeyguard(mToken); } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) { if (isKeyguardAllowed(userId)) { mWindowManager.reenableKeyguard(mToken); } else { // If keyguard is not secure and it is locked, dismiss the keyguard before // disabling it, which avoids the platform to think the keyguard is still on. if (mWindowManager.isKeyguardLocked() && !mWindowManager.isKeyguardSecure()) { mWindowManager.dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { Slog.i(TAG, "setKeyguardState: failed to dismiss keyguard"); } @Override public void onDismissSucceeded() throws RemoteException { mHandler.post( () -> mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG)); } @Override public void onDismissCancelled() throws RemoteException { Slog.i(TAG, "setKeyguardState: dismiss cancelled"); } }, null); } else { mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); } } } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); } } /** * Helper method for locking the device immediately. This may be necessary when the device * leaves the pinned mode. */ private void lockKeyguardIfNeeded() { try { boolean shouldLockKeyguard = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, USER_CURRENT) != 0; if (shouldLockKeyguard) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(null /* callback */, null /* message */); getLockPatternUtils().requireCredentialEntry(USER_ALL); } } catch (Settings.SettingNotFoundException e) { // No setting, don't lock. } } /** * Translates from LockTask feature flags to StatusBarManager disable and disable2 flags. * @param lockTaskFlags Bitfield of flags as per * {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} * @return A {@link Pair} of {@link StatusBarManager#disable(int)} and * {@link StatusBarManager#disable2(int)} flags */ @VisibleForTesting Pair<Integer, Integer> getStatusBarDisableFlags(int lockTaskFlags) { // Everything is disabled by default int flags1 = StatusBarManager.DISABLE_MASK; int flags2 = StatusBarManager.DISABLE2_MASK; for (int i = STATUS_BAR_FLAG_MAP_LOCKED.size() - 1; i >= 0; i--) { Pair<Integer, Integer> statusBarFlags = STATUS_BAR_FLAG_MAP_LOCKED.valueAt(i); if ((STATUS_BAR_FLAG_MAP_LOCKED.keyAt(i) & lockTaskFlags) != 0) { flags1 &= ~statusBarFlags.first; flags2 &= ~statusBarFlags.second; } } // Some flags are not used for LockTask purposes, so we mask them flags1 &= STATUS_BAR_MASK_LOCKED; return new Pair<>(flags1, flags2); } /** * Gets the cached value of LockTask feature flags for a specific user. */ private int getLockTaskFeaturesForUser(int userId) { return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); } // 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; } // Should only be called on the handler thread @Nullable private IDevicePolicyManager getDevicePolicyManager() { if (mDevicePolicyManager == null) { mDevicePolicyManager = IDevicePolicyManager.Stub.asInterface( ServiceManager.checkService(DEVICE_POLICY_SERVICE)); if (mDevicePolicyManager == null) { Slog.w(TAG, "warning: no DEVICE_POLICY_SERVICE"); } } return mDevicePolicyManager; } @NonNull private LockPatternUtils getLockPatternUtils() { if (mLockPatternUtils == null) { // We don't preserve the LPU object to save memory return new LockPatternUtils(mContext); } return mLockPatternUtils; } @Nullable private TelecomManager getTelecomManager() { if (mTelecomManager == null) { // We don't preserve the TelecomManager object to save memory return mContext.getSystemService(TelecomManager.class); } return mTelecomManager; } public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "LockTaskController"); prefix = prefix + " "; pw.println(prefix + "mLockTaskModeState=" + lockTaskModeToString()); pw.println(prefix + "mLockTaskModeTasks="); for (int i = 0; i < mLockTaskModeTasks.size(); ++i) { pw.println(prefix + " #" + i + " " + mLockTaskModeTasks.get(i)); } pw.println(prefix + "mLockTaskPackages (userId:packages)="); for (int i = 0; i < mLockTaskPackages.size(); ++i) { pw.println(prefix + " u" + mLockTaskPackages.keyAt(i) + ":" + Arrays.toString(mLockTaskPackages.valueAt(i))); } } private String lockTaskModeToString() { switch (mLockTaskModeState) { case LOCK_TASK_MODE_LOCKED: return "LOCKED"; case LOCK_TASK_MODE_PINNED: return "PINNED"; case LOCK_TASK_MODE_NONE: return "NONE"; default: return "unknown=" + mLockTaskModeState; } } }