/*
|
* 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.wm;
|
|
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.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
|
import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
|
import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
|
import static com.android.server.wm.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
|
import static com.android.server.wm.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.os.UserHandle;
|
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.am.ActivityManagerService;
|
import com.android.server.statusbar.StatusBarManagerInternal;
|
|
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 ActivityTaskManagerService lock
|
* held.
|
*
|
* @see Activity#startLockTask()
|
* @see Activity#stopLockTask()
|
*/
|
public class LockTaskController {
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "LockTaskController" : TAG_ATM;
|
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 LockTaskToken();
|
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;
|
|
/**
|
* Stores the user for which we're trying to dismiss the keyguard and then subsequently
|
* disable it.
|
*
|
* Tracking this ensures we don't mistakenly disable the keyguard if we've stopped trying to
|
* between the dismiss request and when it succeeds.
|
*
|
* Must only be accessed from the Handler thread.
|
*/
|
private int mPendingDisableFromDismiss = UserHandle.USER_NULL;
|
|
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.getTaskRecord();
|
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 ActivityTaskManagerService#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 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.mRootActivityContainer.resumeFocusedStacksTopActivities();
|
}
|
|
/**
|
* 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 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 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.mRootActivityContainer.resumeFocusedStacksTopActivities();
|
final ActivityStack stack = task.getStack();
|
if (stack != null) {
|
stack.getDisplay().mDisplayContent.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.mRootActivityContainer.getChildCount() - 1;
|
displayNdx >= 0; --displayNdx) {
|
mSupervisor.mRootActivityContainer.getChildAt(displayNdx).onLockTaskPackagesUpdated();
|
}
|
|
final ActivityRecord r = mSupervisor.mRootActivityContainer.topRunningActivity();
|
final TaskRecord task = (r != null) ? r.getTaskRecord() : 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.mRootActivityContainer.resumeFocusedStacksTopActivities();
|
}
|
}
|
|
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) {
|
mPendingDisableFromDismiss = UserHandle.USER_NULL;
|
if (lockTaskModeState == LOCK_TASK_MODE_NONE) {
|
mWindowManager.reenableKeyguard(mToken, userId);
|
|
} else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
|
if (isKeyguardAllowed(userId)) {
|
mWindowManager.reenableKeyguard(mToken, userId);
|
} 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(userId)) {
|
mPendingDisableFromDismiss = userId;
|
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(
|
() -> {
|
if (mPendingDisableFromDismiss == userId) {
|
mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG,
|
userId);
|
mPendingDisableFromDismiss = UserHandle.USER_NULL;
|
}
|
});
|
}
|
|
@Override
|
public void onDismissCancelled() throws RemoteException {
|
Slog.i(TAG, "setKeyguardState: dismiss cancelled");
|
}
|
}, null);
|
} else {
|
mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId);
|
}
|
}
|
|
} else { // lockTaskModeState == LOCK_TASK_MODE_PINNED
|
mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG, userId);
|
}
|
}
|
|
/**
|
* Helper method for locking the device immediately. This may be necessary when the device
|
* leaves the pinned mode.
|
*/
|
private void lockKeyguardIfNeeded() {
|
if (shouldLockKeyguard()) {
|
mWindowManager.lockNow(null);
|
mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
|
getLockPatternUtils().requireCredentialEntry(USER_ALL);
|
}
|
}
|
|
private boolean shouldLockKeyguard() {
|
// This functionality should be kept consistent with
|
// com.android.settings.security.ScreenPinningSettings (see b/127605586)
|
try {
|
return Settings.Secure.getIntForUser(
|
mContext.getContentResolver(),
|
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, USER_CURRENT) != 0;
|
} catch (Settings.SettingNotFoundException e) {
|
// Log to SafetyNet for b/127605586
|
android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, "");
|
return getLockPatternUtils().isSecure(USER_CURRENT);
|
}
|
}
|
|
/**
|
* 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;
|
}
|
}
|
|
/** Marker class for the token used to disable keyguard. */
|
static class LockTaskToken extends Binder {
|
private LockTaskToken() {
|
}
|
}
|
}
|