diff options
14 files changed, 1012 insertions, 501 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index a0082e4e57bb..86f07a7b72fe 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4294,28 +4294,6 @@ public class ActivityManager { } /** - * @hide - */ - public void startLockTaskMode(int taskId) { - try { - getService().startLockTaskModeById(taskId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * @hide - */ - public void stopLockTaskMode() { - try { - getService().stopLockTaskMode(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Return whether currently in lock task mode. When in this mode * no new tasks can be created or switched to. * diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index fa9d7ca8ebc5..e0e4aeb33668 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -391,7 +391,6 @@ interface IActivityManager { // Start of L transactions String getTagForIntentSender(in IIntentSender sender, in String prefix); boolean startUserInBackground(int userid); - void startLockTaskModeById(int taskId); void startLockTaskModeByToken(in IBinder token); void stopLockTaskMode(); boolean isInLockTaskMode(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index c44e1db367b8..cfe0a4a6005b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -504,7 +504,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { // If we recently long-pressed the other button then they were // long-pressed 'together' if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { - activityManager.stopLockTaskMode(); + activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; @@ -522,7 +522,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } else if (touchExplorationEnabled && inLockTaskMode) { // When in accessibility mode a long press that is recents (not back) // should stop lock task. - activityManager.stopLockTaskMode(); + activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 47f3ec69fd03..5b2b76a936e3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; @@ -173,8 +174,6 @@ import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; -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.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; @@ -355,10 +354,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; -import com.android.server.job.JobSchedulerInternal; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; - import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -404,14 +399,15 @@ import com.android.server.ThreadPriorityBooster; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.firewall.IntentFirewall; +import com.android.server.job.JobSchedulerInternal; import com.android.server.pm.Installer; import com.android.server.pm.Installer.InstallerException; -import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.PinnedStackWindowController; import com.android.server.wm.WindowManagerService; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; -import java.text.SimpleDateFormat; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -429,6 +425,7 @@ import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -696,6 +693,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ String mDeviceOwnerName; + /** + * The controller for all operations related to locktask. + */ + final LockTaskController mLockTaskController; + final UserController mUserController; final AppErrors mAppErrors; @@ -2562,6 +2564,7 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager = wm; mStackSupervisor.setWindowManager(wm); mActivityStarter.setWindowManager(wm); + mLockTaskController.setWindowManager(wm); } public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) { @@ -2689,6 +2692,7 @@ public class ActivityManagerService extends IActivityManager.Stub mUiHandler = injector.getUiHandler(null); mUserController = null; mVrController = null; + mLockTaskController = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2786,6 +2790,7 @@ public class ActivityManagerService extends IActivityManager.Stub new TaskChangeNotificationController(this, mStackSupervisor, mHandler); mActivityStarter = new ActivityStarter(this, mStackSupervisor); mRecentTasks = new RecentTasks(this, mStackSupervisor); + mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -4979,12 +4984,10 @@ public class ActivityManagerService extends IActivityManager.Stub } // Do not allow task to finish if last task in lockTask mode. Launchable priv-apps can // finish. - if (tr.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV && rootR == r && - mStackSupervisor.isLastLockedTask(tr)) { - Slog.i(TAG, "Not finishing task in lock task mode"); - mStackSupervisor.showLockTaskToast(); + if (mLockTaskController.activityBlockedFromFinish(r)) { return false; } + if (mController != null) { // Find the first activity that is not finishing. ActivityRecord next = r.getStack().topRunningActivityLocked(token, 0); @@ -5110,9 +5113,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Do not allow task to finish if last task in lockTask mode. Launchable priv-apps // can finish. final TaskRecord task = r.getTask(); - if (task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV && - mStackSupervisor.isLastLockedTask(task) && task.getRootActivity() == r) { - mStackSupervisor.showLockTaskToast(); + if (mLockTaskController.activityBlockedFromFinish(r)) { return false; } return task.getStack().finishActivityAffinityLocked(r); @@ -10372,8 +10373,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.d(TAG, "Could not find task for id: "+ taskId); return; } - if (mStackSupervisor.isLockTaskModeViolation(task)) { - mStackSupervisor.showLockTaskToast(); + if (mLockTaskController.isLockTaskModeViolation(task)) { Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); return; } @@ -10820,6 +10820,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void updateLockTaskPackages(int userId, String[] packages) { + // TODO: move this into LockTaskController final int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != SYSTEM_UID) { enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES, @@ -10829,17 +10830,21 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" + Arrays.toString(packages)); mLockTaskPackages.put(userId, packages); - mStackSupervisor.onLockTaskPackagesUpdatedLocked(); + mLockTaskController.onLockTaskPackagesUpdated(); } } - - void startLockTaskModeLocked(TaskRecord task) { + private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) { if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task); - if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { + if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { return; } + final ActivityStack stack = mStackSupervisor.getFocusedStack(); + if (stack == null || task != stack.topTask()) { + throw new IllegalArgumentException("Invalid task, not in foreground"); + } + // When a task is locked, dismiss the pinned stack if it exists final PinnedActivityStack pinnedStack = mStackSupervisor.getStack( PINNED_STACK_ID); @@ -10847,63 +10852,26 @@ public class ActivityManagerService extends IActivityManager.Stub mStackSupervisor.removeStackLocked(PINNED_STACK_ID); } - // isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode + // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode // is initiated by system after the pinning request was shown and locked mode is initiated // by an authorized app directly final int callingUid = Binder.getCallingUid(); - boolean isSystemInitiated = callingUid == SYSTEM_UID; long ident = Binder.clearCallingIdentity(); try { - if (!isSystemInitiated) { - task.mLockTaskUid = callingUid; - if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) { - // startLockTask() called by app and task mode is lockTaskModeDefault. - 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; - } - - final ActivityStack stack = mStackSupervisor.getFocusedStack(); - if (stack == null || task != stack.topTask()) { - throw new IllegalArgumentException("Invalid task, not in foreground"); - } - } - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" : - "Locking fully"); - mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ? - ActivityManager.LOCK_TASK_MODE_PINNED : - ActivityManager.LOCK_TASK_MODE_LOCKED, - "startLockTask", true); + mLockTaskController.startLockTaskMode(task, isAppPinning, callingUid); } finally { Binder.restoreCallingIdentity(ident); } } @Override - public void startLockTaskModeById(int taskId) { - synchronized (this) { - final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); - if (task != null) { - startLockTaskModeLocked(task); - } - } - } - - @Override public void startLockTaskModeByToken(IBinder token) { synchronized (this) { final ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r == null) { return; } - final TaskRecord task = r.getTask(); - if (task != null) { - startLockTaskModeLocked(task); - } + startLockTaskModeLocked(r.getTask(), false /* not system initiated */); } } @@ -10914,7 +10882,8 @@ public class ActivityManagerService extends IActivityManager.Stub long ident = Binder.clearCallingIdentity(); try { synchronized (this) { - startLockTaskModeById(taskId); + startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId), + true /* system initiated */); } } finally { Binder.restoreCallingIdentity(ident); @@ -10923,41 +10892,28 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void stopLockTaskMode() { - final TaskRecord lockTask = mStackSupervisor.getLockedTaskLocked(); - if (lockTask == null) { - // Our work here is done. - return; - } + stopLockTaskModeInternal(false /* not system initiated */); + } + + /** + * 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 { + enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "stopSystemLockTaskMode"); + stopLockTaskModeInternal(true /* system initiated */); + } + private void stopLockTaskModeInternal(boolean isSystemRequest) { final int callingUid = Binder.getCallingUid(); - final int lockTaskUid = lockTask.mLockTaskUid; - final int lockTaskModeState = mStackSupervisor.getLockTaskModeState(); - if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE) { - // Done. - return; - } else { - // 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 (checkCallingPermission(MANAGE_ACTIVITY_STACKS) != PERMISSION_GRANTED - && callingUid != lockTaskUid - && (lockTaskUid != 0 || callingUid != lockTask.effectiveUid)) { - throw new SecurityException("Invalid uid, expected " + lockTaskUid - + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid); - } - } long ident = Binder.clearCallingIdentity(); try { - Log.d(TAG, "stopLockTaskMode"); - // Stop lock task synchronized (this) { - mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE, - "stopLockTask", true); + mLockTaskController.stopLockTaskMode(isSystemRequest, 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); @@ -10967,28 +10923,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * 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 { - if (mStackSupervisor.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED) { - stopLockTaskMode(); - } else { - mStackSupervisor.showLockTaskToast(); - } - } - @Override public boolean isInLockTaskMode() { - return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE; + return getLockTaskModeState() != LOCK_TASK_MODE_NONE; } @Override public int getLockTaskModeState() { synchronized (this) { - return mStackSupervisor.getLockTaskModeState(); + return mLockTaskController.getLockTaskModeState(); } } @@ -10999,7 +10942,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (r == null) { return; } - mStackSupervisor.showLockTaskEscapeMessageLocked(r.getTask()); + mLockTaskController.showLockTaskToast(); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 45357cb67fd1..403e01d5ae44 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2224,7 +2224,7 @@ final class ActivityManagerShellCommand extends ShellCommand { int runTaskLock(PrintWriter pw) throws RemoteException { String taskIdStr = getNextArgRequired(); if (taskIdStr.equals("stop")) { - mInterface.stopLockTaskMode(); + mInterface.stopSystemLockTaskMode(); } else { int taskId = Integer.parseInt(taskIdStr); mInterface.startSystemLockTaskMode(taskId); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 4a9b98d7be00..d30d3a579848 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1205,7 +1205,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } boolean isKeyguardLocked = service.isKeyguardLocked(); - boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE; + boolean isCurrentAppLocked = service.getLockTaskModeState() != LOCK_TASK_MODE_NONE; boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null; // Don't return early if !isNotLocked, since we want to throw an exception if the activity // is in an incorrect state diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 896f846feec0..ec313958af73 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3682,7 +3682,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (endTask) { - mStackSupervisor.removeLockedTaskLocked(task); + mService.mLockTaskController.removeLockedTask(task); } } else if (r.state != ActivityState.PAUSING) { // If the activity is PAUSING, we will complete the finish once @@ -4536,8 +4536,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai Slog.i(TAG, "moveTaskToBack: " + tr); // If the task is locked, then show the lock task toast - if (mStackSupervisor.isLockedTask(tr)) { - mStackSupervisor.showLockTaskToast(); + if (!mService.mLockTaskController.checkLockedTask(tr)) { return false; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index e8bc68f21981..476b017dd462 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -20,8 +20,6 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.START_ANY_ACTIVITY; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; 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.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID; @@ -36,20 +34,18 @@ import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Process.SYSTEM_UID; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; import static android.view.Display.TYPE_VIRTUAL; - import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE; @@ -57,10 +53,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE; @@ -85,11 +79,8 @@ import static com.android.server.am.ActivityStack.ActivityState.STOPPING; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; import static com.android.server.am.ActivityStack.STACK_INVISIBLE; import static com.android.server.am.ActivityStack.STACK_VISIBLE; -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 static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT; @@ -109,9 +100,7 @@ import android.app.ActivityOptions; import android.app.AppOpsManager; import android.app.ProfilerInfo; import android.app.ResultInfo; -import android.app.StatusBarManager; import android.app.WaitResult; -import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -134,19 +123,15 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.MediaStore; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; import android.util.ArrayMap; import android.util.ArraySet; @@ -162,9 +147,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.TransferPipe; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; -import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.wm.PinnedStackWindowController; @@ -184,7 +167,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_IDLE = TAG + POSTFIX_IDLE; - private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; @@ -210,17 +192,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5; static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6; static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7; - static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9; - static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10; static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12; - static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13; static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14; static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15; private static final String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay"; - private static final String LOCK_TASK_TAG = "Lock-to-App"; - // Used to indicate if an object (e.g. stack) that we are trying to get // should be created if it doesn't exist already. static final boolean CREATE_IF_NEEDED = true; @@ -288,11 +265,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** Action restriction: launching the activity is restricted by an app op. */ private static final int ACTIVITY_RESTRICTION_APPOP = 2; - /** Status Bar Service **/ - private IBinder mToken = new Binder(); - private IStatusBarService mStatusBarService; - private IDevicePolicyManager mDevicePolicyManager; - // For debugging to make sure the caller when acquiring/releasing our // wake lock is the system process. static final boolean VALIDATE_WAKE_LOCK_CALLER = false; @@ -409,20 +381,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private DisplayManagerInternal mDisplayManagerInternal; private InputManagerInternal mInputManagerInternal; - /** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks - * may be finished until there is only one entry left. If this is empty the system is not - * in lockTask mode. */ - ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>(); - /** 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; - /** - * Notifies the user when entering/exiting lock-task. - */ - private LockTaskNotify mLockTaskNotify; - /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ boolean inResumeTopActivity; @@ -619,34 +577,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mLaunchingActivity.setReferenceCounted(false); } - // This function returns a IStatusBarService. The value is from ServiceManager. - // getService and is cached. - private IStatusBarService getStatusBarService() { - synchronized (mService) { - if (mStatusBarService == null) { - mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); - if (mStatusBarService == null) { - Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE"); - } - } - return mStatusBarService; - } - } - - private IDevicePolicyManager getDevicePolicyManager() { - synchronized (mService) { - if (mDevicePolicyManager == null) { - mDevicePolicyManager = IDevicePolicyManager.Stub.asInterface( - ServiceManager.checkService(Context.DEVICE_POLICY_SERVICE)); - if (mDevicePolicyManager == null) { - Slog.w(TAG, "warning: no DEVICE_POLICY_SERVICE"); - } - } - return mDevicePolicyManager; - } - } - void setWindowManager(WindowManagerService wm) { synchronized (mService) { mWindowManager = wm; @@ -1383,8 +1313,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) { - setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "mLockTaskAuth==LAUNCHABLE", - false); + mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */); } try { @@ -3568,18 +3497,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - 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; - } - } - public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack); pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack); @@ -3588,7 +3505,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser); pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); pw.print(prefix); pw.println("mStacks=" + mStacks); - pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString()); + // TODO: move this to LockTaskController final SparseArray<String[]> packages = mService.mLockTaskPackages; if (packages.size() > 0) { pw.print(prefix); pw.println("mLockTaskPackages (userId:packages)="); @@ -3604,8 +3521,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks); mKeyguardController.dump(pw, prefix); + mService.mLockTaskController.dump(pw, prefix); } /** @@ -4021,45 +3938,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return list; } - TaskRecord getLockedTaskLocked() { - final int top = mLockTaskModeTasks.size() - 1; - if (top >= 0) { - return mLockTaskModeTasks.get(top); - } - return null; - } - - boolean isLockedTask(TaskRecord task) { - return mLockTaskModeTasks.contains(task); - } - - boolean isLastLockedTask(TaskRecord task) { - return mLockTaskModeTasks.size() == 1 && mLockTaskModeTasks.contains(task); - } - - void removeLockedTaskLocked(final TaskRecord task) { - if (!mLockTaskModeTasks.remove(task)) { - return; - } - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTaskLocked: removed " + task); - if (mLockTaskModeTasks.isEmpty()) { - // Last one. - if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task + - " last task, reverting locktask mode. Callers=" + Debug.getCallers(3)); - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.arg1 = task.userId; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mHandler.sendMessage(lockTaskMsg); - } - } - void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId, int preferredDisplayId, int actualStackId) { handleNonResizableTaskIfNeeded(task, preferredStackId, preferredDisplayId, actualStackId, false /* forceNonResizable */); } - private void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId, + void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId, int preferredDisplayId, int actualStackId, boolean forceNonResizable) { final boolean isSecondaryDisplayPreferred = (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY) @@ -4118,152 +4003,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void showLockTaskToast() { - if (mLockTaskNotify != null) { - mLockTaskNotify.showToast(mLockTaskModeState); - } - } - - void showLockTaskEscapeMessageLocked(TaskRecord task) { - if (mLockTaskModeTasks.contains(task)) { - mHandler.sendEmptyMessage(SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG); - } - } - - void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason, - boolean andResume) { - if (task == null) { - // Take out of lock task mode if necessary - final TaskRecord lockedTask = getLockedTaskLocked(); - if (lockedTask != null) { - removeLockedTaskLocked(lockedTask); - if (!mLockTaskModeTasks.isEmpty()) { - // There are locked tasks remaining, can only finish this task, not unlock it. - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, - "setLockTaskModeLocked: Tasks remaining, can't unlock"); - lockedTask.performClearTaskLocked(); - resumeFocusedStackTopActivityLocked(); - return; - } - } - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, - "setLockTaskModeLocked: No tasks to unlock. Callers=" + Debug.getCallers(4)); - return; - } - - // Should have already been checked, but do it again. - if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, - "setLockTaskModeLocked: Can't lock due to auth"); - return; - } - if (isLockTaskModeViolation(task)) { - Slog.e(TAG_LOCKTASK, "setLockTaskMode: Attempt to start an unauthorized lock task."); - return; - } - - if (mLockTaskModeTasks.isEmpty()) { - // First locktask. - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.obj = task.intent.getComponent().getPackageName(); - lockTaskMsg.arg1 = task.userId; - lockTaskMsg.what = LOCK_TASK_START_MSG; - lockTaskMsg.arg2 = lockTaskModeState; - mHandler.sendMessage(lockTaskMsg); - } - // Add it or move it to the top. - if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskModeLocked: Locking to " + task + - " Callers=" + Debug.getCallers(4)); - mLockTaskModeTasks.remove(task); - mLockTaskModeTasks.add(task); - - if (task.mLockTaskUid == -1) { - task.mLockTaskUid = task.effectiveUid; - } - - if (andResume) { - findTaskToMoveToFrontLocked(task, 0, null, reason, - lockTaskModeState != LOCK_TASK_MODE_NONE); - resumeFocusedStackTopActivityLocked(); - mWindowManager.executeAppTransition(); - } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { - handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY, - task.getStackId(), true /* forceNonResizable */); - } - } - - boolean isLockTaskModeViolation(TaskRecord task) { - return isLockTaskModeViolation(task, false); - } - - boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) { - if (getLockedTaskLocked() == task && !isNewClearTask) { - return false; - } - final int lockTaskAuth = task.mLockTaskAuth; - switch (lockTaskAuth) { - case LOCK_TASK_AUTH_DONT_LOCK: - return !mLockTaskModeTasks.isEmpty(); - case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: - case LOCK_TASK_AUTH_LAUNCHABLE: - case LOCK_TASK_AUTH_WHITELISTED: - return false; - case LOCK_TASK_AUTH_PINNABLE: - // Pinnable tasks can't be launched on top of locktask tasks. - return !mLockTaskModeTasks.isEmpty(); - default: - Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth); - return true; - } - } - - void onLockTaskPackagesUpdatedLocked() { - boolean didSomething = 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 (wasWhitelisted && !isWhitelisted) { - // Lost whitelisting authorization. End it now. - if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " + - lockedTask + " mLockTaskAuth=" + lockedTask.lockTaskAuthToString()); - removeLockedTaskLocked(lockedTask); - lockedTask.performClearTaskLocked(); - didSomething = true; - } - } - for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; - for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = stacks.get(stackNdx); - stack.onLockTaskPackagesUpdatedLocked(); - } - } - final ActivityRecord r = 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); - setLockTaskModeLocked(task, ActivityManager.LOCK_TASK_MODE_LOCKED, "package updated", - false); - didSomething = true; - } - if (didSomething) { - resumeFocusedStackTopActivityLocked(); - } - } - - int getLockTaskModeState() { - return mLockTaskModeState; - } - void activityRelaunchedLocked(IBinder token) { mWindowManager.notifyAppRelaunchingFinished(token); if (mService.isSleepingOrShuttingDownLocked()) { @@ -4446,78 +4185,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D case HANDLE_DISPLAY_REMOVED: { handleDisplayRemoved(msg.arg1); } break; - case LOCK_TASK_START_MSG: { - // When lock task starts, we disable the status bars. - try { - if (mLockTaskNotify == null) { - mLockTaskNotify = new LockTaskNotify(mService.mContext); - } - mLockTaskNotify.show(true); - mLockTaskModeState = msg.arg2; - if (getStatusBarService() != null) { - int flags = 0; - if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { - flags = StatusBarManager.DISABLE_MASK - & (~StatusBarManager.DISABLE_BACK); - } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { - flags = StatusBarManager.DISABLE_MASK - & (~StatusBarManager.DISABLE_BACK) - & (~StatusBarManager.DISABLE_HOME) - & (~StatusBarManager.DISABLE_RECENT); - } - getStatusBarService().disable(flags, mToken, - mService.mContext.getPackageName()); - } - mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); - if (getDevicePolicyManager() != null) { - getDevicePolicyManager().notifyLockTaskModeChanged(true, - (String)msg.obj, msg.arg1); - } - } catch (RemoteException ex) { - throw new RuntimeException(ex); - } - } break; - case LOCK_TASK_END_MSG: { - // When lock task ends, we enable the status bars. - try { - if (getStatusBarService() != null) { - getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken, - mService.mContext.getPackageName()); - } - mWindowManager.reenableKeyguard(mToken); - if (getDevicePolicyManager() != null) { - getDevicePolicyManager().notifyLockTaskModeChanged(false, null, - msg.arg1); - } - if (mLockTaskNotify == null) { - mLockTaskNotify = new LockTaskNotify(mService.mContext); - } - mLockTaskNotify.show(false); - try { - boolean shouldLockKeyguard = Settings.Secure.getInt( - mService.mContext.getContentResolver(), - Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0; - if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { - mWindowManager.lockNow(null); - mWindowManager.dismissKeyguard(null /* callback */); - new LockPatternUtils(mService.mContext) - .requireCredentialEntry(UserHandle.USER_ALL); - } - } catch (SettingNotFoundException e) { - // No setting, don't lock. - } - } catch (RemoteException ex) { - throw new RuntimeException(ex); - } finally { - mLockTaskModeState = LOCK_TASK_MODE_NONE; - } - } break; - case SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG: { - if (mLockTaskNotify == null) { - mLockTaskNotify = new LockTaskNotify(mService.mContext); - } - mLockTaskNotify.showToast(LOCK_TASK_MODE_PINNED); - } break; case LAUNCH_TASK_BEHIND_COMPLETE: { synchronized (mService) { ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 761f9eef65fd..235477e24a27 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1033,10 +1033,9 @@ class ActivityStarter { // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but // still needs to be a lock task mode violation since the task gets cleared out and // the device would otherwise leave the locked task. - if (mSupervisor.isLockTaskModeViolation(reusedActivity.getTask(), + if (mService.mLockTaskController.isLockTaskModeViolation(reusedActivity.getTask(), (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { - mSupervisor.showLockTaskToast(); Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -1813,7 +1812,7 @@ class ActivityStarter { mStartActivity.setTaskToAffiliateWith(taskToAffiliate); } - if (mSupervisor.isLockTaskModeViolation(mStartActivity.getTask())) { + if (mService.mLockTaskController.isLockTaskModeViolation(mStartActivity.getTask())) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -1832,7 +1831,7 @@ class ActivityStarter { } private int setTaskFromSourceRecord() { - if (mSupervisor.isLockTaskModeViolation(mSourceRecord.getTask())) { + if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } @@ -1926,7 +1925,7 @@ class ActivityStarter { private int setTaskFromInTask() { // The caller is asking that the new activity be started in an explicit // task it has provided to us. - if (mSupervisor.isLockTaskModeViolation(mInTask)) { + if (mService.mLockTaskController.isLockTaskModeViolation(mInTask)) { Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java new file mode 100644 index 000000000000..c03ac86a2bf9 --- /dev/null +++ b/services/core/java/com/android/server/am/LockTaskController.java @@ -0,0 +1,572 @@ +/* + * 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.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.StatusBarManager.DISABLE_BACK; +import static android.app.StatusBarManager.DISABLE_HOME; +import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.StatusBarManager.DISABLE_NONE; +import static android.app.StatusBarManager.DISABLE_RECENT; +import static android.content.Context.DEVICE_POLICY_SERVICE; +import static android.content.Context.STATUS_BAR_SERVICE; +import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED; +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.admin.IDevicePolicyManager; +import android.content.Context; +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.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +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; + +/** + * 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 = DISABLE_MASK + & (~DISABLE_BACK); + @VisibleForTesting + static final int STATUS_BAR_MASK_PINNED = DISABLE_MASK + & (~DISABLE_BACK) + & (~DISABLE_HOME) + & (~DISABLE_RECENT); + + /** 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; + + /** + * Helper that is responsible for showing the right toast when a disallowed activity operation + * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in + * fully locked mode we only show that unlocking is blocked. + */ + @VisibleForTesting + LockTaskNotify mLockTaskNotify; + + /** + * The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks + * may be finished until there is only one entry left. If this is empty the system is not + * in lockTask mode. + */ + private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>(); + + /** + * 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; + + /** + * 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 can be moved to the back of the stack. Locked tasks cannot be + * moved to the back of the stack. + */ + boolean checkLockedTask(TaskRecord task) { + if (mLockTaskModeTasks.contains(task)) { + showLockTaskToast(); + return true; + } + return false; + } + + /** + * @return whether the given activity is blocked from finishing, because it is the root activity + * of the last locked task and finishing it would mean that lock task mode is ended illegally. + */ + boolean activityBlockedFromFinish(ActivityRecord activity) { + TaskRecord task = activity.getTask(); + if (activity == task.getRootActivity() + && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV + && mLockTaskModeTasks.size() == 1 + && mLockTaskModeTasks.contains(task)) { + Slog.i(TAG, "Not finishing task in lock task mode"); + showLockTaskToast(); + return true; + } + return false; + } + + /** + * @return whether the requested task is allowed 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 allowed to be launched. + */ + boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) { + if (isLockTaskModeViolationInternal(task, isNewClearTask)) { + showLockTaskToast(); + return true; + } + return false; + } + + 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 (getLockedTask() == task && !isNewClearTask) { + // If the task is already at the top and won't be cleared, then allow the operation + return false; + } + final int lockTaskAuth = task.mLockTaskAuth; + switch (lockTaskAuth) { + case LOCK_TASK_AUTH_DONT_LOCK: + return !mLockTaskModeTasks.isEmpty(); + case LOCK_TASK_AUTH_LAUNCHABLE_PRIV: + case LOCK_TASK_AUTH_LAUNCHABLE: + case LOCK_TASK_AUTH_WHITELISTED: + return false; + case LOCK_TASK_AUTH_PINNABLE: + // Pinnable tasks can't be launched on top of locktask tasks. + return !mLockTaskModeTasks.isEmpty(); + default: + Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth); + return true; + } + } + + /** + * Stop the current lock task mode. + * + * @param isSystemInitiated indicates whether this request was initiated by the system via + * {@link ActivityManagerService#stopSystemLockTaskMode()}. + * @param callingUid the caller that requested the end of lock task mode. + * @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(boolean isSystemInitiated, int callingUid) { + final TaskRecord lockTask = getLockedTask(); + if (lockTask == null || mLockTaskModeState == LOCK_TASK_MODE_NONE) { + // Our work here is done. + return; + } + + if (isSystemInitiated && mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { + // As system can only start app pinning, we also only let it unlock in this mode. + showLockTaskToast(); + return; + } + + // 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 (!isSystemInitiated && callingUid != lockTask.mLockTaskUid + && (lockTask.mLockTaskUid != 0 || callingUid != lockTask.effectiveUid)) { + throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid + + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid); + } + + clearLockTaskMode("stopLockTask"); + } + + /** + * Remove the given task from the locked task list. If this was the last task in the list, + * lock task mode is stopped. + */ + void removeLockedTask(final TaskRecord task) { + if (!mLockTaskModeTasks.remove(task)) { + return; + } + if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTask: removed " + task); + if (mLockTaskModeTasks.isEmpty()) { + // Last one. + if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task + + " last task, reverting locktask mode. Callers=" + Debug.getCallers(3)); + mHandler.post(() -> performStopLockTask(task.userId)); + } + } + + /** + * Remove the topmost task from the locked task list. If this is the last task in the list, it + * will result in the end of locked task mode. + */ + void clearLockTaskMode(String reason) { + // Take out of lock task mode if necessary + final TaskRecord lockedTask = getLockedTask(); + if (lockedTask != null) { + removeLockedTask(lockedTask); + if (!mLockTaskModeTasks.isEmpty()) { + // There are locked tasks remaining, can only finish this task, not unlock it. + if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, + "setLockTaskMode: Tasks remaining, can't unlock"); + lockedTask.performClearTaskLocked(); + mSupervisor.resumeFocusedStackTopActivityLocked(); + return; + } + } + if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, + "setLockTaskMode: No tasks to unlock. Callers=" + Debug.getCallers(4)); + } + + // 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 { + if (getStatusBarService() != null) { + getStatusBarService().disable(DISABLE_NONE, mToken, + mContext.getPackageName()); + } + mWindowManager.reenableKeyguard(mToken); + if (getDevicePolicyManager() != null) { + getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); + } + getLockTaskNotify().show(false); + try { + boolean shouldLockKeyguard = Settings.Secure.getInt( + mContext.getContentResolver(), + LOCK_TO_APP_EXIT_LOCKED) != 0; + if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { + mWindowManager.lockNow(null); + mWindowManager.dismissKeyguard(null /* callback */); + getLockPatternUtils().requireCredentialEntry(USER_ALL); + } + } catch (Settings.SettingNotFoundException e) { + // No setting, don't lock. + } + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } finally { + mLockTaskModeState = LOCK_TASK_MODE_NONE; + } + } + + /** + * Show the lock task violation toast. + */ + void showLockTaskToast() { + mHandler.post(() -> getLockTaskNotify().showToast(mLockTaskModeState)); + } + + // Starting lock task + + /** + * Method to start lock task mode on a given task. + * + * @param task the task that should be locked. + * @param isSystemInitiated indicates whether this request was initiated by the system via + * {@link ActivityManagerService#startSystemLockTaskMode(int)}. + * @param callingUid the caller that requested the launch of lock task mode. + */ + void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemInitiated, + int callingUid) { + if (!isSystemInitiated) { + 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 isSystemInitiated 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, isSystemInitiated ? "Locking pinned" : "Locking fully"); + setLockTaskMode(task, isSystemInitiated ? 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; + } + + if (mLockTaskModeTasks.isEmpty()) { + // Start lock task on the handler thread + mHandler.post(() -> performStartLockTask( + task.intent.getComponent().getPackageName(), + task.userId, + lockTaskModeState)); + } + + // Add it or move it to the top. + if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task + + " Callers=" + Debug.getCallers(4)); + mLockTaskModeTasks.remove(task); + mLockTaskModeTasks.add(task); + + if (task.mLockTaskUid == -1) { + task.mLockTaskUid = task.effectiveUid; + } + + if (andResume) { + mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason, + lockTaskModeState != LOCK_TASK_MODE_NONE); + mSupervisor.resumeFocusedStackTopActivityLocked(); + mWindowManager.executeAppTransition(); + } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { + mSupervisor.handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY, + task.getStackId(), 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 { + getLockTaskNotify().show(true); + mLockTaskModeState = lockTaskModeState; + if (getStatusBarService() != null) { + int flags = 0; + if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { + flags = STATUS_BAR_MASK_LOCKED; + } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { + flags = STATUS_BAR_MASK_PINNED; + } + getStatusBarService().disable(flags, mToken, mContext.getPackageName()); + } + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + if (getDevicePolicyManager() != null) { + getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId); + } + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Called when the list of packages whitelisted for lock task mode is changed. Any currently + * locked tasks that got removed from the whitelist will be finished. + */ + // TODO: Missing unit tests + void onLockTaskPackagesUpdated() { + boolean didSomething = 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 (wasWhitelisted && !isWhitelisted) { + // Lost whitelisting authorization. End it now. + if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " + + lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString()); + removeLockedTask(lockedTask); + lockedTask.performClearTaskLocked(); + didSomething = true; + } + } + + for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mSupervisor.getChildAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.onLockTaskPackagesUpdatedLocked(); + } + } + 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); + didSomething = true; + } + if (didSomething) { + mSupervisor.resumeFocusedStackTopActivityLocked(); + } + } + + /** + * @return the topmost locked task + */ + private TaskRecord getLockedTask() { + final int top = mLockTaskModeTasks.size() - 1; + if (top >= 0) { + return mLockTaskModeTasks.get(top); + } + return null; + } + + // 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; + } + + // Should only be called on the handler thread + @NonNull + private LockTaskNotify getLockTaskNotify() { + if (mLockTaskNotify == null) { + mLockTaskNotify = new LockTaskNotify(mContext); + } + return mLockTaskNotify; + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString()); + pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks); + } + + 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; + } + } +} diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 88d894449185..dd29b9549acd 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -121,7 +121,7 @@ import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static java.lang.Integer.MAX_VALUE; -final class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener { +class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; @@ -469,7 +469,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } void removeWindowContainer() { - mService.mStackSupervisor.removeLockedTaskLocked(this); + mService.mLockTaskController.removeLockedTask(this); mWindowContainerController.removeContainer(); if (!StackId.persistTaskBounds(getStackId())) { // Reset current bounds for task whose bounds shouldn't be persisted so it uses @@ -1394,7 +1394,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta /** * Completely remove all activities associated with an existing task. */ - final void performClearTaskLocked() { + void performClearTaskLocked() { mReuseTask = true; performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY); mReuseTask = false; @@ -1526,7 +1526,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta " mLockTaskAuth=" + lockTaskAuthToString()); } - boolean isLockTaskWhitelistedLocked() { + private boolean isLockTaskWhitelistedLocked() { String pkg = (realActivity != null) ? realActivity.getPackageName() : null; if (pkg == null) { return false; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 7e9a2ac91127..29445673dfff 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -171,8 +171,6 @@ class UserController { @GuardedBy("mLock") private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks; - private volatile UserManagerService mUserManager; - private final LockPatternUtils mLockPatternUtils; UserController(ActivityManagerService service) { @@ -831,8 +829,9 @@ class UserController { } if (foreground) { - mInjector.getActivityStackSupervisor().setLockTaskModeLocked( - null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false); + // TODO: I don't think this does what the caller think it does. Seems to only + // remove one locked task and won't work if multiple locked tasks are present. + mInjector.getLockTaskController().clearLockTaskMode("startUser"); } final UserInfo userInfo = getUserInfo(userId); @@ -1777,5 +1776,9 @@ class UserController { ActivityStackSupervisor getActivityStackSupervisor() { return mService.mStackSupervisor; } + + LockTaskController getLockTaskController() { + return mService.mLockTaskController; + } } } diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java new file mode 100644 index 000000000000..e98e5bfb110e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java @@ -0,0 +1,355 @@ +/* + * 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.os.Process.SYSTEM_UID; +import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED; +import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_PINNED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.StatusBarManager; +import android.app.admin.IDevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; + +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 org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +/** + * Unit tests for {@link LockTaskController}. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.LockTaskControllerTest + */ +@Presubmit +@SmallTest +public class LockTaskControllerTest { + private static final String TEST_PACKAGE_NAME = "com.test.package"; + private static final String TEST_CLASS_NAME = TEST_PACKAGE_NAME + ".TestClass"; + private static final int TEST_USER_ID = 123; + private static final int TEST_UID = 10467; + + @Mock private ActivityStackSupervisor mSupervisor; + @Mock private IDevicePolicyManager mDevicePolicyManager; + @Mock private IStatusBarService mStatusBarService; + @Mock private WindowManagerService mWindowManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private LockTaskNotify mLockTaskNotify; + @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + + private LockTaskController mLockTaskController; + private Context mContext; + private String mLockToAppSetting; + + @Before + public void setUp() throws Exception { + // This property is used to allow mocking of package private classes with mockito + System.setProperty("dexmaker.share_classloader", "true"); + + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getTargetContext(); + mLockToAppSetting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mLockTaskController = new LockTaskController(mContext, mSupervisor, + new ImmediatelyExecuteHandler()); + + mLockTaskController.setWindowManager(mWindowManager); + mLockTaskController.mStatusBarService = mStatusBarService; + mLockTaskController.mDevicePolicyManager = mDevicePolicyManager; + mLockTaskController.mLockPatternUtils = mLockPatternUtils; + mLockTaskController.mLockTaskNotify = mLockTaskNotify; + + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); + } + + @After + public void tearDown() throws Exception { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, mLockToAppSetting); + } + + @Test + public void testPreconditions() { + // GIVEN nothing has happened + + // THEN current lock task mode should be NONE + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + } + + @Test + public void testStartLockTaskMode_once() throws Exception { + // GIVEN a task record with whitelisted auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + + // WHEN calling setLockTaskMode for LOCKED mode without resuming + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN the lock task mode state should be LOCKED + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN the task should be locked + assertTrue(mLockTaskController.checkLockedTask(tr)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED); + } + + @Test + public void testStartLockTaskMode_twice() throws Exception { + // GIVEN two task records with whitelisted auth + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + + // WHEN calling setLockTaskMode for LOCKED mode on both tasks + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // THEN the lock task mode state should be LOCKED + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN neither of the tasks should be able to move to back of stack + assertTrue(mLockTaskController.checkLockedTask(tr1)); + assertTrue(mLockTaskController.checkLockedTask(tr2)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED); + } + + @Test + public void testStartLockTaskMode_pinningRequest() throws Exception { + // GIVEN a task record that is not whitelisted, i.e. with pinned auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + + // WHEN calling startLockTaskMode + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN a pinning request should be shown + verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt()); + } + + @Test + public void testStartLockTaskMode_pinnedBySystem() throws Exception { + // GIVEN a task record with pinned auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + + // WHEN the system calls startLockTaskMode + mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); + + // THEN the lock task mode state should be PINNED + assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState()); + // THEN the task should be locked + assertTrue(mLockTaskController.checkLockedTask(tr)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_PINNED); + } + + @Test + public void testLockTaskViolation() throws Exception { + // GIVEN one task records with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN it's not a lock task violation to try and launch this task without clearing + assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false)); + + // THEN it's a lock task violation to launch another task that is not whitelisted + assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_PINNABLE))); + // THEN it's a lock task violation to launch another task that is disallowed from lock task + assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_DONT_LOCK))); + + // THEN it's no a lock task violation to launch another task that is whitelisted + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_WHITELISTED))); + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE))); + // THEN it's not a lock task violation to launch another task that is priv launchable + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV))); + } + + @Test + public void testStopLockTaskMode() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN the same caller calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(false, TEST_UID); + + // THEN the lock task mode should be NONE + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the task should no longer be locked + assertFalse(mLockTaskController.checkLockedTask(tr)); + // THEN lock task mode should have been finished + verifyLockTaskStopped(times(1)); + } + + @Test(expected = SecurityException.class) + public void testStopLockTaskMode_DifferentCaller() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN a different caller calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(false, TEST_UID + 1); + + // THEN security exception should be thrown, because different caller tried to unlock + } + + @Test + public void testStopLockTaskMode_SystemCaller() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN system calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(true, SYSTEM_UID); + + // THEN a lock tash toast should be shown + verify(mLockTaskNotify).showToast(LOCK_TASK_MODE_LOCKED); + // THEN lock task mode should still be active + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + } + + @Test + public void testStopLockTaskMode_twoTasks() throws Exception { + // GIVEN two task records with whitelisted auth that is in lock task mode + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // WHEN calling stopLockTaskMode + mLockTaskController.stopLockTaskMode(false, TEST_UID); + + // THEN the lock task mode should still be active + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN the first task should still be locked + assertTrue(mLockTaskController.checkLockedTask(tr1)); + // THEN the top task should no longer be locked + assertFalse(mLockTaskController.checkLockedTask(tr2)); + // THEN lock task mode should not have been finished + verifyLockTaskStopped(never()); + } + + @Test + public void testStopLockTaskMode_pinned() throws Exception { + // GIVEN one task records that is in pinned mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); + // GIVEN that the keyguard is required to show after unlocking + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1); + + // WHEN calling stopLockTask + mLockTaskController.stopLockTaskMode(true, SYSTEM_UID); + + // THEN the lock task mode should no longer be active + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the task should no longer be locked + assertFalse(mLockTaskController.checkLockedTask(tr)); + // THEN lock task mode should have been finished + verifyLockTaskStopped(times(1)); + // THEN the keyguard should be shown + verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL); + } + + private TaskRecord getTaskRecord(int lockTaskAuth) { + TaskRecord tr = mock(TaskRecord.class); + tr.mLockTaskAuth = lockTaskAuth; + tr.intent = new Intent() + .setComponent(new ComponentName(TEST_PACKAGE_NAME, TEST_CLASS_NAME)); + tr.userId = TEST_USER_ID; + return tr; + } + + private void verifyLockTaskStarted(int statusBarMask) throws Exception { + // THEN the keyguard should have been disabled + verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); + // THEN the status bar should have been disabled + verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class), + eq(mContext.getPackageName())); + // THEN the DO/PO should be informed about the operation + verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME, + TEST_USER_ID); + } + + private void verifyLockTaskStopped(VerificationMode mode) throws Exception { + // THEN the keyguard should have been disabled + verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class)); + // THEN the status bar should have been disabled + verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE), + any(IBinder.class), eq(mContext.getPackageName())); + // THEN the DO/PO should be informed about the operation + verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID); + } + + /** + * Special handler implementation that executes any message / runnable posted immediately on the + * thread that it's posted on rather than enqueuing them on its looper. + */ + private static class ImmediatelyExecuteHandler extends Handler { + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + if (msg.getCallback() != null) { + msg.getCallback().run(); + } + return true; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 7a4746a7f54b..4475de5aa14e 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -16,7 +16,6 @@ package com.android.server.am; -import android.app.ActivityManager; import android.app.IUserSwitchObserver; import android.content.Context; import android.content.IIntentReceiver; @@ -57,16 +56,15 @@ import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_M import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG; import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class UserControllerTest extends AndroidTestCase { @@ -117,11 +115,7 @@ public class UserControllerTest extends AndroidTestCase { Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true); - Mockito.verify(mInjector.getActivityStackSupervisor()).setLockTaskModeLocked( - nullable(TaskRecord.class), - eq(ActivityManager.LOCK_TASK_MODE_NONE), - anyString(), - anyBoolean()); + Mockito.verify(mInjector.getLockTaskController()).clearLockTaskMode(anyString()); startForegroundUserAssertions(); } @@ -131,11 +125,7 @@ public class UserControllerTest extends AndroidTestCase { Mockito.verify( mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); - Mockito.verify(mInjector.getActivityStackSupervisor(), never()).setLockTaskModeLocked( - nullable(TaskRecord.class), - eq(ActivityManager.LOCK_TASK_MODE_NONE), - anyString(), - anyBoolean()); + verifyZeroInteractions(mInjector.getLockTaskController()); startBackgroundUserAssertions(); } @@ -324,6 +314,7 @@ public class UserControllerTest extends AndroidTestCase { UserManagerInternal userManagerInternalMock; WindowManagerService windowManagerMock; ActivityStackSupervisor activityStackSupervisor; + LockTaskController lockTaskController; private Context mCtx; List<Intent> sentIntents = new ArrayList<>(); @@ -337,6 +328,7 @@ public class UserControllerTest extends AndroidTestCase { userManagerInternalMock = mock(UserManagerInternal.class); windowManagerMock = mock(WindowManagerService.class); activityStackSupervisor = mock(ActivityStackSupervisor.class); + lockTaskController = mock(LockTaskController.class); } @Override @@ -399,6 +391,10 @@ public class UserControllerTest extends AndroidTestCase { ActivityStackSupervisor getActivityStackSupervisor() { return activityStackSupervisor; } + + LockTaskController getLockTaskController() { + return lockTaskController; + } } private static class TestHandler extends Handler { |