diff --git a/api/test-current.txt b/api/test-current.txt index 66d6af9b67c78..00c8fdcc77a24 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -111,6 +111,7 @@ package android.app { method public void setLaunchActivityType(int); method public void setLaunchTaskId(int); method public void setLaunchWindowingMode(int); + method public void setTaskAlwaysOnTop(boolean); method public void setTaskOverlay(boolean, boolean); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4aacf4879c43a..7fd0211215c52 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -208,6 +208,12 @@ public class ActivityOptions { private static final String KEY_PENDING_INTENT_LAUNCH_FLAGS = "android.activity.pendingIntentLaunchFlags"; + /** + * See {@link #setTaskAlwaysOnTop}. + * @hide + */ + private static final String KEY_TASK_ALWAYS_ON_TOP = "android.activity.alwaysOnTop"; + /** * See {@link #setTaskOverlay}. * @hide @@ -337,6 +343,7 @@ public class ActivityOptions { private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; private boolean mLockTaskMode = false; private boolean mDisallowEnterPictureInPictureWhileLaunching; + private boolean mTaskAlwaysOnTop; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; private boolean mAvoidMoveToFront; @@ -971,6 +978,7 @@ public class ActivityOptions { mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED); mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mPendingIntentLaunchFlags = opts.getInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, 0); + mTaskAlwaysOnTop = opts.getBoolean(KEY_TASK_ALWAYS_ON_TOP, false); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false); @@ -1299,6 +1307,22 @@ public class ActivityOptions { return mPendingIntentLaunchFlags; } + /** + * Set's whether the task for the activity launched with this option should always be on top. + * @hide + */ + @TestApi + public void setTaskAlwaysOnTop(boolean alwaysOnTop) { + mTaskAlwaysOnTop = alwaysOnTop; + } + + /** + * @hide + */ + public boolean getTaskAlwaysOnTop() { + return mTaskAlwaysOnTop; + } + /** * Set's whether the activity launched with this option should be a task overlay. That is the * activity will always be the top activity of the task. @@ -1556,6 +1580,9 @@ public class ActivityOptions { if (mPendingIntentLaunchFlags != 0) { b.putInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, mPendingIntentLaunchFlags); } + if (mTaskAlwaysOnTop) { + b.putBoolean(KEY_TASK_ALWAYS_ON_TOP, mTaskAlwaysOnTop); + } if (mTaskOverlay) { b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); } diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 9d6c3d64aaf24..a448e132bcc9d 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -31,6 +31,11 @@ interface ITaskOrganizerController { */ void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode); + /** + * Unregisters a previously registered task organizer. + */ + void unregisterTaskOrganizer(ITaskOrganizer organizer); + /** * Apply multiple WindowContainer operations at once. * @param organizer If non-null this transaction will use the synchronization diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index c7a139176f6cb..14ca7cbae4b39 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -27,8 +27,10 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER; import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WaitResult.LAUNCH_STATE_COLD; import static android.app.WaitResult.LAUNCH_STATE_HOT; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -167,6 +169,8 @@ class ActivityStarter { // The display to launch the activity onto, barring any strong reason to do otherwise. private int mPreferredDisplayId; + // The windowing mode to apply to the root task, if possible + private int mPreferredWindowingMode; private Task mInTask; @VisibleForTesting @@ -538,6 +542,7 @@ class ActivityStarter { mStartFlags = starter.mStartFlags; mSourceRecord = starter.mSourceRecord; mPreferredDisplayId = starter.mPreferredDisplayId; + mPreferredWindowingMode = starter.mPreferredWindowingMode; mInTask = starter.mInTask; mAddingToTask = starter.mAddingToTask; @@ -1493,8 +1498,6 @@ class ActivityStarter { setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor, restrictedBgActivity); - final int preferredWindowingMode = mLaunchParams.mWindowingMode; - computeLaunchingTaskFlags(); computeSourceStack(); @@ -1564,6 +1567,14 @@ class ActivityStarter { if (!mAvoidMoveToFront && mDoResume) { mTargetStack.moveToFront("reuseOrNewTask"); + if (mOptions != null) { + if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED) { + mTargetStack.setWindowingMode(mPreferredWindowingMode); + } + if (mOptions.getTaskAlwaysOnTop()) { + mTargetStack.setAlwaysOnTop(true); + } + } } mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName, @@ -1627,7 +1638,7 @@ class ActivityStarter { // Update the recent tasks list immediately when the activity starts mSupervisor.mRecentTasks.add(mStartActivity.getTask()); mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), - preferredWindowingMode, mPreferredDisplayId, mTargetStack); + mPreferredWindowingMode, mPreferredDisplayId, mTargetStack); return START_SUCCESS; } @@ -1680,9 +1691,10 @@ class ActivityStarter { mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r, sourceRecord, mOptions, PHASE_BOUNDS, mLaunchParams); - mPreferredDisplayId = - mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId - : DEFAULT_DISPLAY; + mPreferredDisplayId = mLaunchParams.hasPreferredDisplay() + ? mLaunchParams.mPreferredDisplayId + : DEFAULT_DISPLAY; + mPreferredWindowingMode = mLaunchParams.mWindowingMode; } private int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) { @@ -2006,6 +2018,7 @@ class ActivityStarter { mStartFlags = 0; mSourceRecord = null; mPreferredDisplayId = INVALID_DISPLAY; + mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED; mInTask = null; mAddingToTask = false; @@ -2054,9 +2067,10 @@ class ActivityStarter { // after we located a reusable task (which might be resided in another display). mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r, sourceRecord, options, PHASE_DISPLAY, mLaunchParams); - mPreferredDisplayId = - mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId - : DEFAULT_DISPLAY; + mPreferredDisplayId = mLaunchParams.hasPreferredDisplay() + ? mLaunchParams.mPreferredDisplayId + : DEFAULT_DISPLAY; + mPreferredWindowingMode = mLaunchParams.mWindowingMode; mLaunchMode = r.launchMode; @@ -2098,7 +2112,7 @@ class ActivityStarter { } if (mOptions != null) { - if (mOptions.getLaunchTaskId() != -1 && mOptions.getTaskOverlay()) { + if (mOptions.getLaunchTaskId() != INVALID_TASK_ID && mOptions.getTaskOverlay()) { r.setTaskOverlay(true); if (!mOptions.canTaskOverlayResume()) { final Task task = mRootWindowContainer.anyTaskForId( @@ -2291,6 +2305,15 @@ class ActivityStarter { * if not or an ActivityRecord with the task into which the new activity should be added. */ private Task getReusableTask() { + // If a target task is specified, try to reuse that one + if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) { + Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId()); + if (launchTask != null) { + return launchTask; + } + return null; + } + // We may want to try to place the new activity in to an existing task. We always // do this if the target activity is singleTask or singleInstance; we will also do // this if NEW_TASK has been requested, and there is not an additional qualifier telling @@ -2304,12 +2327,7 @@ class ActivityStarter { // same component, then instead of launching bring that one to the front. putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null; ActivityRecord intentActivity = null; - if (mOptions != null && mOptions.getLaunchTaskId() != -1) { - Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId()); - if (launchTask != null) { - return launchTask; - } - } else if (putIntoExistingTask) { + if (putIntoExistingTask) { if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) { // There can be one and only one instance of single instance activity in the // history, and it is always in its own unique task, so we do a special search. diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index ee36db9d94ee1..a4bdfb351c1f9 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -87,7 +88,8 @@ class InsetsStateController { final InsetsSourceProvider provider = target.getControllableInsetProvider(); final @InternalInsetsType int type = provider != null ? provider.getSource().getType() : ITYPE_INVALID; - return getInsetsForTypeAndWindowingMode(type, target.getWindowingMode()); + return getInsetsForTypeAndWindowingMode(type, target.getWindowingMode(), + target.isAlwaysOnTop()); } InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { @@ -95,7 +97,9 @@ class InsetsStateController { final WindowToken token = mDisplayContent.getWindowToken(attrs.token); final @WindowingMode int windowingMode = token != null ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED; - return getInsetsForTypeAndWindowingMode(type, windowingMode); + final boolean alwaysOnTop = token != null + ? token.isAlwaysOnTop() : false; + return getInsetsForTypeAndWindowingMode(type, windowingMode, alwaysOnTop); } private static @InternalInsetsType int getInsetsTypeForWindowType(int type) { @@ -113,7 +117,7 @@ class InsetsStateController { /** @see #getInsetsForDispatch */ private InsetsState getInsetsForTypeAndWindowingMode(@InternalInsetsType int type, - @WindowingMode int windowingMode) { + @WindowingMode int windowingMode, boolean isAlwaysOnTop) { InsetsState state = mState; if (type != ITYPE_INVALID) { @@ -147,7 +151,8 @@ class InsetsStateController { } } - if (WindowConfiguration.isFloating(windowingMode)) { + if (WindowConfiguration.isFloating(windowingMode) + || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { state = new InsetsState(state); state.removeSource(ITYPE_STATUS_BAR); state.removeSource(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 8f09f3faae29c..cc52cb445a1aa 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -231,7 +231,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } - void unregisterTaskOrganizer(ITaskOrganizer organizer) { + @Override + public void unregisterTaskOrganizer(ITaskOrganizer organizer) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); state.unlinkDeath(); if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index c449049164e48..2acb6476453cf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -51,6 +51,7 @@ public class ActivityOptionsTest { opts.setLaunchTaskId(Integer.MAX_VALUE); opts.setLockTaskEnabled(true); opts.setRotationAnimationHint(ROTATION_ANIMATION_ROTATE); + opts.setTaskAlwaysOnTop(true); opts.setTaskOverlay(true, true); opts.setSplitScreenCreateMode(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT); Bundle optsBundle = opts.toBundle(); @@ -67,6 +68,7 @@ public class ActivityOptionsTest { assertEquals(Integer.MAX_VALUE, restoredOpts.getLaunchTaskId()); assertTrue(restoredOpts.getLockTaskMode()); assertEquals(ROTATION_ANIMATION_ROTATE, restoredOpts.getRotationAnimationHint()); + assertTrue(restoredOpts.getTaskAlwaysOnTop()); assertTrue(restoredOpts.getTaskOverlay()); assertTrue(restoredOpts.canTaskOverlayResume()); assertEquals(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index a380ece61935b..bfb126f7052ce 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; @@ -130,6 +131,21 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_NAVIGATION_BAR)); } + @Test + public void testStripForDispatch_multiwindow_alwaysOnTop() { + final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); + final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); + getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); + app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + app.setAlwaysOnTop(true); + + assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_STATUS_BAR)); + assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_NAVIGATION_BAR)); + } + @Test public void testImeForDispatch() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");