diff options
| author | 2019-11-05 10:26:24 -0800 | |
|---|---|---|
| committer | 2020-01-31 10:26:45 -0800 | |
| commit | 0037e5f9011ab8e56fdcd4bf1d1702f57ab20b21 (patch) | |
| tree | c02eda80f5cc2e858fc76ab4e01ada35d9d83120 | |
| parent | 39df263cc5e2bde767709e158baa4d145c60384e (diff) | |
Add TaskTile concept to Window Manager
This adds the concept of a TaskTile to the WM. Due to
complexities in the current Stack/Task relationship, tiles
can't actually be part of the hierarchy, so the Stack
level has to internally resolve configurations as if they
were.
The TaskTiles themselves *are* ActivityStacks though from
the client/sysui perspective, though.
Bug: 133381284
Test: Added TaskTileTests
Change-Id: I9baad5ec899b4fab323a36c1533a40081727a2f7
21 files changed, 1050 insertions, 259 deletions
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index dd9a2bcf9c2c..58bff7f4dc04 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -158,6 +158,24 @@ public class ActivityTaskManager { } }; + /** @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public static ITaskOrganizerController getTaskOrganizerController() { + return ITaskOrganizerControllerSingleton.get(); + } + + private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton = + new Singleton<ITaskOrganizerController>() { + @Override + protected ITaskOrganizerController create() { + try { + return getService().getTaskOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + }; + /** * Sets the windowing mode for a specific task. Only works on tasks of type * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 85fa7c1cdb54..503f5c56c617 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -29,6 +29,7 @@ import android.app.IProcessObserver; import android.app.IRequestFinishCallback; import android.app.IServiceConnection; import android.app.IStopUserCallback; +import android.app.ITaskOrganizerController; import android.app.ITaskStackListener; import android.app.IUiAutomationConnection; import android.app.IUidObserver; @@ -71,7 +72,6 @@ import android.view.IRecentsAnimationRunner; import android.view.ITaskOrganizer; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; -import android.view.WindowContainerTransaction; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -123,8 +123,6 @@ interface IActivityTaskManager { int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId); - void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode); - boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType, int userId); @@ -224,7 +222,6 @@ interface IActivityTaskManager { void setTaskResizeable(int taskId, int resizeableMode); void toggleFreeformWindowingMode(in IBinder token); void resizeTask(int taskId, in Rect bounds, int resizeMode); - void applyContainerTransaction(in WindowContainerTransaction t); void moveStackToDisplay(int stackId, int displayId); void removeStack(int stackId); @@ -364,6 +361,11 @@ interface IActivityTaskManager { in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds); /** + * Returns an interface enabling the management of task organizers. + */ + ITaskOrganizerController getTaskOrganizerController(); + + /** * Sets whether we are currently in an interactive split screen resize operation where we * are changing the docked stack size. */ diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl new file mode 100644 index 000000000000..168f782d02a6 --- /dev/null +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2020, 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 android.app; + +import android.app.ActivityManager; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; + +/** @hide */ +interface ITaskOrganizerController { + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode); + + /** Apply multiple WindowContainer operations at once. */ + void applyContainerTransaction(in WindowContainerTransaction t); + + /** Creates a persistent root task in WM for a particular windowing-mode. */ + ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); + + /** Deletes a persistent root task in WM */ + boolean deleteRootTask(IWindowContainer task); + + /** Get the root task which contains the current ime target */ + IWindowContainer getImeTarget(int display); + + /** + * Set's the root task to launch new tasks into on a display. {@code null} means no launch root + * and thus new tasks just end up directly on the display. + */ + void setLaunchRoot(int displayId, in IWindowContainer root); +} diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index fe9c64038909..662ca6eb2c19 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -16,6 +16,8 @@ package android.app; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -25,6 +27,7 @@ import android.content.res.Configuration; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; +import android.view.IWindowContainer; /** * Stores information about a particular Task. @@ -138,6 +141,19 @@ public class TaskInfo { @UnsupportedAppUsage public final Configuration configuration = new Configuration(); + /** + * Used as an opaque identifier for this task. + * @hide + */ + @NonNull + public IWindowContainer token; + + /** + * The activity type of the top activity in this task. + * @hide + */ + public @WindowConfiguration.ActivityType int topActivityType; + TaskInfo() { // Do nothing } @@ -160,6 +176,11 @@ public class TaskInfo { } } + /** @hide */ + public boolean isResizable() { + return resizeMode != RESIZE_MODE_UNRESIZEABLE; + } + /** * Reads the TaskInfo from a parcel. */ @@ -186,6 +207,8 @@ public class TaskInfo { supportsSplitScreenMultiWindow = source.readBoolean(); resizeMode = source.readInt(); configuration.readFromParcel(source); + token = IWindowContainer.Stub.asInterface(source.readStrongBinder()); + topActivityType = source.readInt(); } /** @@ -221,6 +244,8 @@ public class TaskInfo { dest.writeBoolean(supportsSplitScreenMultiWindow); dest.writeInt(resizeMode); configuration.writeToParcel(dest, flags); + dest.writeStrongInterface(token); + dest.writeInt(topActivityType); } @Override @@ -234,6 +259,8 @@ public class TaskInfo { + " numActivities=" + numActivities + " lastActiveTime=" + lastActiveTime + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow - + " resizeMode=" + resizeMode; + + " resizeMode=" + resizeMode + + " token=" + token + + " topActivityType=" + topActivityType; } } diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl index e92aafed6f22..5ccdd30a9d97 100644 --- a/core/java/android/view/ITaskOrganizer.aidl +++ b/core/java/android/view/ITaskOrganizer.aidl @@ -26,8 +26,7 @@ import android.app.ActivityManager; * {@hide} */ oneway interface ITaskOrganizer { - void taskAppeared(in IWindowContainer container, - in ActivityManager.RunningTaskInfo taskInfo); + void taskAppeared(in ActivityManager.RunningTaskInfo taskInfo); void taskVanished(in IWindowContainer container); /** @@ -35,4 +34,19 @@ oneway interface ITaskOrganizer { * ActivityTaskManagerService#applyTaskOrganizerTransaction */ void transactionReady(int id, in SurfaceControl.Transaction t); + + /** + * Will fire when core attributes of a Task's info change. Relevant properties include the + * {@link WindowConfiguration.ActivityType} and whether it is resizable. + * + * This is used, for example, during split-screen. The flow for starting is: Something sends an + * Intent with windowingmode. Then WM finds a matching root task and launches the new task into + * it. This causes the root task's info to change because now it has a task when it didn't + * before. The default Divider implementation interprets this as a request to enter + * split-screen mode and will move all other Tasks into the secondary root task. When WM + * applies this change, it triggers an info change in the secondary root task because it now + * has children. The Divider impl looks at the info and can see that the secondary root task + * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX. + */ + void onTaskInfoChanged(in ActivityManager.RunningTaskInfo info); }
\ No newline at end of file diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index c55caa3213d8..33f21f211a1a 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -86,6 +86,17 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Set the smallestScreenWidth of a container. + */ + public WindowContainerTransaction setSmallestScreenWidthDp(IWindowContainer container, + int widthDp) { + Change cfg = getOrCreateChange(container.asBinder()); + cfg.mConfiguration.smallestScreenWidthDp = widthDp; + cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index ddf0117987dd..0d72d84d1649 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -341,6 +341,9 @@ class ActivityStack extends Task implements BoundsAnimationTarget { private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; + // TODO(task-hierarchy): remove when tiles can be actual parents + TaskTile mTile = null; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -638,11 +641,20 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } @Override + public void resolveOverrideConfiguration(Configuration newParentConfig) { + super.resolveOverrideConfiguration(newParentConfig); + if (mTile != null) { + // If this is a virtual child of a tile, simulate the parent-child relationship + mTile.updateResolvedConfig(getResolvedOverrideConfiguration()); + } + } + + @Override public void onConfigurationChanged(Configuration newParentConfig) { // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are // particularly for ActivityStack, like preventing bounds changes when inheriting certain // windowing mode. - if (!isRootTask()) { + if (!isRootTask() || this instanceof TaskTile) { super.onConfigurationChanged(newParentConfig); return; } @@ -3944,7 +3956,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { ? ((WindowContainer) newParent).getDisplayContent() : null; final DisplayContent oldDisplay = oldParent != null ? ((WindowContainer) oldParent).getDisplayContent() : null; - super.onParentChanged(newParent, oldParent); if (display != null && inSplitScreenPrimaryWindowingMode() @@ -3963,6 +3974,11 @@ class ActivityStack extends Task implements BoundsAnimationTarget { if (oldDisplay != null && oldDisplay.isRemoving()) { postReparent(); } + if (mTile != null && getSurfaceControl() != null) { + // by now, the TaskStack should already have been reparented, so we can reparent its + // surface here + reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl()); + } } void reparent(DisplayContent newParent, boolean onTop) { @@ -4000,7 +4016,16 @@ class ActivityStack extends Task implements BoundsAnimationTarget { @Override void getRelativeDisplayedPosition(Point outPos) { - super.getRelativeDisplayedPosition(outPos); + // check for tile which is "virtually" a parent. + if (mTile != null) { + final Rect dispBounds = getDisplayedBounds(); + outPos.set(dispBounds.left, dispBounds.top); + final Rect parentBounds = mTile.getBounds(); + outPos.offset(-parentBounds.left, -parentBounds.top); + } else { + super.getRelativeDisplayedPosition(outPos); + } + final int outset = getStackOutset(); outPos.x -= outset; outPos.y -= outset; @@ -4010,6 +4035,16 @@ class ActivityStack extends Task implements BoundsAnimationTarget { if (mSurfaceControl == null) { return; } + if (mTile != null) { + // Tile controls crop, so the app needs to be able to draw its background outside of + // the stack bounds for when the tile crop gets bigger than the stack. + if (mLastSurfaceSize.equals(0, 0)) { + return; + } + transaction.setWindowCrop(mSurfaceControl, null); + mLastSurfaceSize.set(0, 0); + return; + } final Rect stackBounds = getDisplayedBounds(); int width = stackBounds.width(); @@ -4033,6 +4068,9 @@ class ActivityStack extends Task implements BoundsAnimationTarget { @Override void onDisplayChanged(DisplayContent dc) { + if (mTile != null && dc != mTile.getDisplay()) { + mTile.removeChild(this); + } super.onDisplayChanged(dc); if (isRootTask()) { updateSurfaceBounds(); @@ -4848,6 +4886,42 @@ class ActivityStack extends Task implements BoundsAnimationTarget { return shouldSleepActivities() || mAtmService.mShuttingDown; } + TaskTile getTile() { + return mTile; + } + + /** + * Don't call this directly. instead use {@link TaskTile#addChild} or + * {@link TaskTile#removeChild}. + */ + void setTile(TaskTile tile) { + TaskTile origTile = mTile; + mTile = tile; + final ConfigurationContainer parent = getParent(); + if (parent != null) { + onConfigurationChanged(parent.getConfiguration()); + } + + // Reparent to tile surface or back to original parent + if (getSurfaceControl() == null) { + return; + } + if (mTile != null) { + reparentSurfaceControl(getPendingTransaction(), mTile.getSurfaceControl()); + } else if (mTile == null && origTile != null) { + reparentSurfaceControl(getPendingTransaction(), getParentSurfaceControl()); + } + } + + @Override + void removeImmediately() { + // TODO(task-hierarchy): remove this override when tiles are in hierarchy + if (mTile != null) { + mTile.removeChild(this); + } + super.removeImmediately(); + } + @Override public void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f019013eedf1..d6e707795d50 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -144,6 +144,7 @@ import android.app.IApplicationThread; import android.app.IAssistDataReceiver; import android.app.INotificationManager; import android.app.IRequestFinishCallback; +import android.app.ITaskOrganizerController; import android.app.ITaskStackListener; import android.app.Notification; import android.app.NotificationManager; @@ -225,10 +226,8 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; -import android.view.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; -import android.view.WindowContainerTransaction; import android.view.WindowManager; import com.android.internal.R; @@ -292,7 +291,6 @@ import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -344,10 +342,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** This activity is being relaunched due to a free-resize operation. */ public static final int RELAUNCH_REASON_FREE_RESIZE = 2; - /** Flag indicating that an applied transaction may have effected lifecycle */ - private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; - private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; - Context mContext; /** @@ -669,8 +663,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * Stores the registration and state of TaskOrganizers in use. */ - TaskOrganizerController mTaskOrganizerController = - new TaskOrganizerController(this, mGlobalLock); + TaskOrganizerController mTaskOrganizerController = new TaskOrganizerController(this); private int mDeviceOwnerUid = Process.INVALID_UID; @@ -1286,15 +1279,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { - enforceCallerIsRecentsOrHasPermission( - MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()"); - synchronized (mGlobalLock) { - mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode); - } - } - - @Override public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { int callingUid = Binder.getCallingUid(); if (UserHandle.getAppId(callingUid) != SYSTEM_UID) { @@ -3304,116 +3288,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - private int sanitizeAndApplyChange(WindowContainer container, - WindowContainerTransaction.Change change) { - if (!(container instanceof Task || container instanceof ActivityStack)) { - throw new RuntimeException("Invalid token in task transaction"); - } - // The "client"-facing API should prevent bad changes; however, just in case, sanitize - // masks here. - int configMask = change.getConfigSetMask(); - int windowMask = change.getWindowSetMask(); - configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION - | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; - windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS; - int effects = 0; - if (configMask != 0) { - Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); - c.setTo(change.getConfiguration(), configMask, windowMask); - container.onRequestedOverrideConfigurationChanged(c); - // TODO(b/145675353): remove the following once we could apply new bounds to the - // pinned stack together with its children. - resizePinnedStackIfNeeded(container, configMask, windowMask, c); - effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; - } - if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { - if (container.setFocusable(change.getFocusable())) { - effects |= TRANSACT_EFFECTS_LIFECYCLE; - } - } - return effects; - } - - private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, - int windowMask, Configuration config) { - if ((container instanceof ActivityStack) - && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) - && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) { - final ActivityStack stack = (ActivityStack) container; - if (stack.inPinnedWindowingMode()) { - stack.resize(config.windowConfiguration.getBounds(), - null /* tempTaskBounds */, null /* tempTaskInsetBounds */, - PRESERVE_WINDOWS, true /* deferResume */); - } - } - } - - private int applyWindowContainerChange(WindowContainer wc, - WindowContainerTransaction.Change c) { - int effects = sanitizeAndApplyChange(wc, c); - - Rect enterPipBounds = c.getEnterPipBounds(); - if (enterPipBounds != null) { - Task tr = (Task) wc; - mStackSupervisor.updatePictureInPictureMode(tr, - enterPipBounds, true); - } - return effects; - } - - @Override - public void applyContainerTransaction(WindowContainerTransaction t) { - mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); - if (t == null) { - return; - } - long ident = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - int effects = 0; - deferWindowLayout(); - try { - ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); - Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = - t.getChanges().entrySet().iterator(); - while (entries.hasNext()) { - final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = - entries.next(); - final WindowContainer wc = WindowContainer.RemoteToken.fromBinder( - entry.getKey()).getContainer(); - int containerEffect = applyWindowContainerChange(wc, entry.getValue()); - effects |= containerEffect; - // Lifecycle changes will trigger ensureConfig for everything. - if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 - && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { - haveConfigChanges.add(wc); - } - } - if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { - // Already calls ensureActivityConfig - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { - final PooledConsumer f = PooledLambda.obtainConsumer( - ActivityRecord::ensureActivityConfiguration, - PooledLambda.__(ActivityRecord.class), 0, - false /* preserveWindow */); - try { - for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { - haveConfigChanges.valueAt(i).forAllActivities(f); - } - } finally { - f.recycle(); - } - } - } finally { - continueWindowLayout(); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - @Override public boolean releaseActivityInstance(IBinder token) { synchronized (mGlobalLock) { @@ -4442,6 +4316,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public ITaskOrganizerController getTaskOrganizerController() { + mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, + "getTaskOrganizerController()"); + return mTaskOrganizerController; + } + /** * Check that we have the features required for VR-related API calls, and throw an exception if * not. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6e479b2b53e0..9f4cd8892e0a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -207,6 +207,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; +import android.view.ITaskOrganizer; import android.view.IWindow; import android.view.InputChannel; import android.view.InputDevice; @@ -664,6 +665,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final RootWindowContainer.FindTaskResult mTmpFindTaskResult = new RootWindowContainer.FindTaskResult(); + // When non-null, new stacks get put into this tile. + TaskTile mLaunchTile = null; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -4275,8 +4279,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @VisibleForTesting ActivityStack getTopStack() { - return mTaskContainers.getChildCount() > 0 - ? mTaskContainers.getChildAt(mTaskContainers.getChildCount() - 1) : null; + // TODO(task-hierarchy): Just grab index -1 once tiles are in hierarchy. + for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) { + final ActivityStack child = mTaskContainers.getChildAt(i); + if (child instanceof TaskTile) { + continue; + } + return child; + } + return null; } int getIndexOf(ActivityStack stack) { @@ -4318,6 +4329,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void addStackReferenceIfNeeded(ActivityStack stack) { + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + if (stack instanceof TaskTile) { + return; + } if (stack.isActivityTypeHome()) { if (mRootHomeTask != null) { if (!stack.isDescendantOf(mRootHomeTask)) { @@ -4735,6 +4750,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mSplitScreenDividerAnchor = null; } } + + @Override + void onChildPositionChanged(WindowContainer child) { + // TODO(task-hierarchy): Move functionality to TaskTile when it's a proper parent. + TaskTile tile = ((ActivityStack) child).getTile(); + if (tile == null) { + return; + } + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + tile, false /* force */); + } } private final class AboveAppWindowContainers extends NonAppWindowContainers { @@ -6271,6 +6297,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean isTopNotPinnedStack(ActivityStack stack) { + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + if (stack instanceof TaskTile) { + return false; + } for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack current = getStackAt(i); if (!current.inPinnedWindowingMode()) { @@ -6685,6 +6715,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return getHomeActivityForUser(mRootWindowContainer.mCurrentUser); } + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + void addTile(TaskTile tile) { + mTaskContainers.addChild(tile, POSITION_BOTTOM); + ITaskOrganizer organizer = mAtmService.mTaskOrganizerController.getTaskOrganizer( + tile.getWindowingMode()); + tile.setTaskOrganizer(organizer); + } + + // TODO(task-hierarchy): Remove when tiles are in hierarchy. + void removeTile(TaskTile tile) { + mTaskContainers.removeChild(tile); + } + @Nullable ActivityRecord getHomeActivityForUser(int userId) { final ActivityStack homeStack = getRootHomeTask(); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index da773143f75c..efe79b36d645 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -526,7 +526,7 @@ public class DisplayRotation { mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout); mIsWaitingForRemoteRotation = false; mDisplayContent.sendNewConfiguration(); - mService.mAtmService.applyContainerTransaction(t); + mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 647be0f7038c..9770947ff2c3 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -388,11 +388,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // surfaces needs to be done immediately. mWindowManager.executeAppTransition(); - // After reordering the stacks, reset the minimized state. At this point, either - // the target activity is now top-most and we will stay minimized (if in - // split-screen), or we will have returned to the app, and the minimized state - // should be reset - mWindowManager.checkSplitScreenMinimizedChanged(true /* animate */); + if (targetStack.getTile() != null) { + // Client state may have changed during the recents animation, so force + // send task info so the client can synchronize its state. + mService.mTaskOrganizerController.dispatchTaskInfoChanged( + targetStack.mTile, true /* force */); + } } catch (Exception e) { Slog.e(TAG, "Failed to clean up recents activity", e); throw e; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index e6fd512df72c..a13399bcb69c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1012,6 +1012,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mWmService.scheduleAnimationLocked(); + // Send any pending task-info changes that were queued-up during a layout deferment + mWmService.mAtmService.mTaskOrganizerController.dispatchPendingTaskInfoChanges(); + if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit: animating=" + mWmService.mAnimator.isAnimating()); @@ -2959,6 +2962,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents(); case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant(); } + // TODO(task-hierarchy): Find another way to differentiate tile from normal stack once it is + // part of the hierarchy + if (stack instanceof TaskTile) { + // Don't launch directly into tiles. + return false; + } // There is a 1-to-1 relationship between stack and task when not in // primary split-windowing mode. if (stack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 36cae1fb9b36..348104a5cfeb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1257,7 +1257,7 @@ class Task extends WindowContainer<WindowContainer> { if (affinityIntent != null) return affinityIntent; // Probably a task that contains other tasks, so return the intent for the top task? final Task topTask = getTopMostTask(); - return topTask != null ? topTask.getBaseIntent() : null; + return (topTask != this && topTask != null) ? topTask.getBaseIntent() : null; } /** Returns the first non-finishing activity from the bottom. */ @@ -3214,7 +3214,8 @@ class Task extends WindowContainer<WindowContainer> { info.taskId = mTaskId; info.displayId = getDisplayId(); info.isRunning = getTopNonFinishingActivity() != null; - info.baseIntent = new Intent(getBaseIntent()); + final Intent baseIntent = getBaseIntent(); + info.baseIntent = baseIntent == null ? new Intent() : baseIntent; info.baseActivity = mReuseActivitiesReport.base != null ? mReuseActivitiesReport.base.intent.getComponent() : null; @@ -3229,6 +3230,10 @@ class Task extends WindowContainer<WindowContainer> { info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); info.resizeMode = mResizeMode; info.configuration.setTo(getConfiguration()); + info.token = mRemoteToken; + // Get's the first non-undefined activity type among this and children. Can't use + // configuration.windowConfiguration because that would only be this level. + info.topActivityType = getActivityType(); } /** @@ -3375,7 +3380,7 @@ class Task extends WindowContainer<WindowContainer> { if (affinity != null) { sb.append(" A="); sb.append(affinity); - } else if (intent != null) { + } else if (intent != null && intent.getComponent() != null) { sb.append(" I="); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null && affinityIntent.getComponent() != null) { @@ -3865,7 +3870,12 @@ class Task extends WindowContainer<WindowContainer> { boolean isControlledByTaskOrganizer() { final Task rootTask = getRootTask(); - return rootTask == this && rootTask.mTaskOrganizer != null; + return rootTask == this && rootTask.mTaskOrganizer != null + // TODO(task-hierarchy): Figure out how to control nested tasks. + // For now, if this is in a tile let WM drive. + && !(rootTask instanceof TaskTile) + && !(rootTask instanceof ActivityStack + && ((ActivityStack) rootTask).getTile() != null); } @Override @@ -3893,6 +3903,9 @@ class Task extends WindowContainer<WindowContainer> { } void setTaskOrganizer(ITaskOrganizer organizer) { + if (mTaskOrganizer == organizer) { + return; + } // Let the old organizer know it has lost control. if (mTaskOrganizer != null) { sendTaskVanished(); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 66c65e226bce..44a6fc936961 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -16,26 +16,51 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + +import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ITaskOrganizerController; +import android.app.WindowConfiguration; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Slog; import android.view.ITaskOrganizer; -import android.view.SurfaceControl; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; + +import com.android.internal.util.function.pooled.PooledConsumer; +import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; /** * Stores the TaskOrganizers associated with a given windowing mode and * their associated state. */ -class TaskOrganizerController { +class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - private WindowManagerGlobalLock mGlobalLock; + /** Flag indicating that an applied transaction may have effected lifecycle */ + private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; + private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; + + private final WindowManagerGlobalLock mGlobalLock; private class DeathRecipient implements IBinder.DeathRecipient { int mWindowingMode; @@ -87,11 +112,20 @@ class TaskOrganizerController { final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); + private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>(); + private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>(); + final ActivityTaskManagerService mService; - TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) { + RunningTaskInfo mTmpTaskInfo; + + TaskOrganizerController(ActivityTaskManagerService atm) { mService = atm; - mGlobalLock = lock; + mGlobalLock = atm.mGlobalLock; + } + + private void enforceStackPermission(String func) { + mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } private void clearIfNeeded(int windowingMode) { @@ -106,26 +140,35 @@ class TaskOrganizerController { * If there was already a TaskOrganizer for this windowing mode it will be evicted * and receive taskVanished callbacks in the process. */ - void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { - if (windowingMode != WINDOWING_MODE_PINNED && - windowingMode != WINDOWING_MODE_MULTI_WINDOW) { - throw new UnsupportedOperationException( - "As of now only Pinned and Multiwindow windowing modes are" - + " supported for registerTaskOrganizer"); - + @Override + public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + if (windowingMode != WINDOWING_MODE_PINNED + && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && windowingMode != WINDOWING_MODE_MULTI_WINDOW) { + throw new UnsupportedOperationException("As of now only Pinned/Split/Multiwindow" + + " windowing modes are supported for registerTaskOrganizer"); } - clearIfNeeded(windowingMode); - DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + enforceStackPermission("registerTaskOrganizer()"); + final long origId = Binder.clearCallingIdentity(); try { - organizer.asBinder().linkToDeath(dr, 0); - } catch (RemoteException e) { - Slog.e(TAG, "TaskOrganizer failed to register death recipient"); - } + synchronized (mGlobalLock) { + clearIfNeeded(windowingMode); + DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } - final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); - mTaskOrganizersForWindowingMode.put(windowingMode, state); + final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + mTaskOrganizersForWindowingMode.put(windowingMode, state); - mTaskOrganizerStates.put(organizer, state); + mTaskOrganizerStates.put(organizer, state); + } + } finally { + Binder.restoreCallingIdentity(origId); + } } ITaskOrganizer getTaskOrganizer(int windowingMode) { @@ -138,7 +181,7 @@ class TaskOrganizerController { private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { try { - organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo()); + organizer.taskAppeared(task.getTaskInfo()); } catch (Exception e) { Slog.e(TAG, "Exception sending taskAppeared callback" + e); } @@ -167,4 +210,254 @@ class TaskOrganizerController { // we do this AFTER sending taskVanished. state.removeTask(task); } + + @Override + public RunningTaskInfo createRootTask(int displayId, int windowingMode) { + enforceStackPermission("createRootTask()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId); + if (display == null) { + return null; + } + final int nextId = display.getNextStackId(); + TaskTile tile = new TaskTile(mService, nextId, windowingMode); + display.addTile(tile); + RunningTaskInfo out = new RunningTaskInfo(); + tile.fillTaskInfo(out); + mLastSentTaskInfos.put(tile, out); + return out; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public boolean deleteRootTask(IWindowContainer token) { + enforceStackPermission("deleteRootTask()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + TaskTile tile = TaskTile.forToken(token.asBinder()); + if (tile == null) { + return false; + } + tile.removeImmediately(); + return true; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + void dispatchPendingTaskInfoChanges() { + if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) { + return; + } + for (int i = 0, n = mPendingTaskInfoChanges.size(); i < n; ++i) { + dispatchTaskInfoChanged(mPendingTaskInfoChanges.get(i), false /* force */); + } + mPendingTaskInfoChanges.clear(); + } + + void dispatchTaskInfoChanged(Task task, boolean force) { + if (!force && mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) { + // Defer task info reporting while layout is deferred. This is because layout defer + // blocks tend to do lots of re-ordering which can mess up animations in receivers. + mPendingTaskInfoChanges.remove(task); + mPendingTaskInfoChanges.add(task); + return; + } + RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task); + if (mTmpTaskInfo == null) { + mTmpTaskInfo = new RunningTaskInfo(); + } + task.fillTaskInfo(mTmpTaskInfo); + boolean changed = lastInfo == null + || mTmpTaskInfo.topActivityType != lastInfo.topActivityType + || mTmpTaskInfo.isResizable() != lastInfo.isResizable(); + if (!(changed || force)) { + return; + } + final RunningTaskInfo newInfo = mTmpTaskInfo; + mLastSentTaskInfos.put(task, mTmpTaskInfo); + // Since we've stored this, clean up the reference so a new one will be created next time. + // Transferring it this way means we only have to construct new RunningTaskInfos when they + // change. + mTmpTaskInfo = null; + + if (task.mTaskOrganizer != null) { + try { + task.mTaskOrganizer.onTaskInfoChanged(newInfo); + } catch (RemoteException e) { + } + } + } + + @Override + public IWindowContainer getImeTarget(int displayId) { + enforceStackPermission("getImeTarget()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent dc = mService.mWindowManager.mRoot + .getDisplayContent(displayId); + if (dc == null || dc.mInputMethodTarget == null) { + return null; + } + // Avoid WindowState#getRootTask() so we don't attribute system windows to a task. + final Task task = dc.mInputMethodTarget.getTask(); + if (task == null) { + return null; + } + ActivityStack rootTask = (ActivityStack) task.getRootTask(); + final TaskTile tile = rootTask.getTile(); + if (tile != null) { + rootTask = tile; + } + return rootTask.mRemoteToken; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public void setLaunchRoot(int displayId, @Nullable IWindowContainer tile) { + enforceStackPermission("setLaunchRoot()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId); + if (display == null) { + return; + } + TaskTile taskTile = tile == null ? null : TaskTile.forToken(tile.asBinder()); + if (taskTile == null) { + display.mLaunchTile = null; + return; + } + if (taskTile.getDisplay() != display) { + throw new RuntimeException("Can't set launch root for display " + displayId + + " to task on display " + taskTile.getDisplay().getDisplayId()); + } + display.mLaunchTile = taskTile; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private int sanitizeAndApplyChange(WindowContainer container, + WindowContainerTransaction.Change change) { + if (!(container instanceof Task)) { + throw new RuntimeException("Invalid token in task transaction"); + } + // The "client"-facing API should prevent bad changes; however, just in case, sanitize + // masks here. + int configMask = change.getConfigSetMask(); + int windowMask = change.getWindowSetMask(); + configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION + | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS; + int effects = 0; + if (configMask != 0) { + Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); + c.setTo(change.getConfiguration(), configMask, windowMask); + container.onRequestedOverrideConfigurationChanged(c); + // TODO(b/145675353): remove the following once we could apply new bounds to the + // pinned stack together with its children. + resizePinnedStackIfNeeded(container, configMask, windowMask, c); + effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; + } + if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { + if (container.setFocusable(change.getFocusable())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + return effects; + } + + private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, + int windowMask, Configuration config) { + if ((container instanceof ActivityStack) + && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) + && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) { + final ActivityStack stack = (ActivityStack) container; + if (stack.inPinnedWindowingMode()) { + stack.resize(config.windowConfiguration.getBounds(), + null /* tempTaskBounds */, null /* tempTaskInsetBounds */, + PRESERVE_WINDOWS, true /* deferResume */); + } + } + } + + private int applyWindowContainerChange(WindowContainer wc, + WindowContainerTransaction.Change c) { + int effects = sanitizeAndApplyChange(wc, c); + + Rect enterPipBounds = c.getEnterPipBounds(); + if (enterPipBounds != null) { + Task tr = (Task) wc; + mService.mStackSupervisor.updatePictureInPictureMode(tr, + enterPipBounds, true); + } + return effects; + } + + @Override + public void applyContainerTransaction(WindowContainerTransaction t) { + enforceStackPermission("applyContainerTransaction()"); + if (t == null) { + return; + } + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + int effects = 0; + mService.deferWindowLayout(); + try { + ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); + Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = + t.getChanges().entrySet().iterator(); + while (entries.hasNext()) { + final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = + entries.next(); + final WindowContainer wc = WindowContainer.RemoteToken.fromBinder( + entry.getKey()).getContainer(); + int containerEffect = applyWindowContainerChange(wc, entry.getValue()); + effects |= containerEffect; + // Lifecycle changes will trigger ensureConfig for everything. + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 + && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + haveConfigChanges.add(wc); + } + } + if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { + // Already calls ensureActivityConfig + mService.mRootWindowContainer.ensureActivitiesVisible( + null, 0, PRESERVE_WINDOWS); + } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { + final PooledConsumer f = PooledLambda.obtainConsumer( + ActivityRecord::ensureActivityConfiguration, + PooledLambda.__(ActivityRecord.class), 0, + false /* preserveWindow */); + try { + for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { + haveConfigChanges.valueAt(i).forAllActivities(f); + } + } finally { + f.recycle(); + } + } + } finally { + mService.continueWindowLayout(); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java new file mode 100644 index 000000000000..add11d69cbaa --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.app.WindowConfiguration; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; + +import java.util.ArrayList; +import java.util.Comparator; + +/** + * A Tile. Right now this acts as a proxy for manipulating non-child stacks. Eventually, this + * can become an actual parent. + */ +// TODO(task-hierarchy): Remove when tasks can nest >2 or when single tasks can handle their +// own lifecycles. +public class TaskTile extends ActivityStack { + private static final String TAG = "TaskTile"; + final ArrayList<WindowContainer> mChildren = new ArrayList<>(); + + private static ActivityInfo createEmptyActivityInfo() { + ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + return info; + } + + TaskTile(ActivityTaskManagerService atmService, int id, int windowingMode) { + super(atmService, id, new Intent() /*intent*/, null /*affinityIntent*/, null /*affinity*/, + null /*rootAffinity*/, null /*realActivity*/, null /*origActivity*/, + false /*rootWasReset*/, false /*autoRemoveRecents*/, false /*askedCompatMode*/, + 0 /*userId*/, 0 /*effectiveUid*/, null /*lastDescription*/, + System.currentTimeMillis(), true /*neverRelinquishIdentity*/, + new ActivityManager.TaskDescription(), id, INVALID_TASK_ID, INVALID_TASK_ID, + 0 /*taskAffiliationColor*/, 0 /*callingUid*/, "" /*callingPackage*/, + RESIZE_MODE_RESIZEABLE, false /*supportsPictureInPicture*/, + false /*_realActivitySuspended*/, false /*userSetupComplete*/, INVALID_MIN_SIZE, + INVALID_MIN_SIZE, createEmptyActivityInfo(), null /*voiceSession*/, + null /*voiceInteractor*/, null /*stack*/); + getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + } + + @Override + void onDisplayChanged(DisplayContent dc) { + mDisplayContent = null; + if (dc != null) { + dc.getPendingTransaction().merge(getPendingTransaction()); + } + mDisplayContent = dc; + // Virtual parent, so don't notify children. + } + + /** + * If there is a disconnection, this will clean up any vestigial surfaces left on the tile + * leash by moving known children to a new surfacecontrol and then removing the old one. + */ + void cleanupSurfaces() { + if (mSurfaceControl == null) { + return; + } + SurfaceControl oldSurface = mSurfaceControl; + WindowContainer parentWin = getParent(); + if (parentWin == null) { + return; + } + mSurfaceControl = parentWin.makeChildSurface(null).setName("TaskTile " + mTaskId + " - " + + getRequestedOverrideWindowingMode()).setContainerLayer().build(); + SurfaceControl.Transaction t = parentWin.getPendingTransaction(); + t.show(mSurfaceControl); + for (int i = 0; i < mChildren.size(); ++i) { + if (mChildren.get(i).getSurfaceControl() == null) { + continue; + } + mChildren.get(i).reparentSurfaceControl(t, mSurfaceControl); + } + t.remove(oldSurface); + } + + @Override + protected void addChild(WindowContainer child, Comparator<WindowContainer> comparator) { + throw new RuntimeException("Improper use of addChild() on Tile"); + } + + @Override + void addChild(WindowContainer child, int index) { + mChildren.add(child); + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(this); + } + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + @Override + void removeChild(WindowContainer child) { + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(null); + } + mChildren.remove(child); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + void removeAllChildren() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child instanceof ActivityStack) { + ((ActivityStack) child).setTile(null); + } + } + mChildren.clear(); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, false /* force */); + } + + @Override + protected int getChildCount() { + // Currently 0 as this isn't a proper hierarchy member yet. + return 0; + } + + @Override + public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { + Configuration c = new Configuration(getRequestedOverrideConfiguration()); + c.windowConfiguration.setWindowingMode(windowingMode); + onRequestedOverrideConfigurationChanged(c); + } + + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + child.onConfigurationChanged(child.getParent().getConfiguration()); + } + } + + /** + * Until this can be part of the hierarchy, the Stack level can use this utility during + * resolveOverrideConfig to simulate inheritance. + */ + void updateResolvedConfig(Configuration inOutResolvedConfig) { + Rect resolveBounds = inOutResolvedConfig.windowConfiguration.getBounds(); + if (resolveBounds == null || resolveBounds.isEmpty()) { + resolveBounds.set(getRequestedOverrideBounds()); + } + int stackMode = inOutResolvedConfig.windowConfiguration.getWindowingMode(); + if (stackMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED + || stackMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { + // Also replace FULLSCREEN because we interpret FULLSCREEN as "fill parent" + inOutResolvedConfig.windowConfiguration.setWindowingMode( + getRequestedOverrideWindowingMode()); + } + if (inOutResolvedConfig.smallestScreenWidthDp + == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + inOutResolvedConfig.smallestScreenWidthDp = + getRequestedOverrideConfiguration().smallestScreenWidthDp; + } + } + + @Override + void fillTaskInfo(TaskInfo info) { + super.fillTaskInfo(info); + WindowContainer top = null; + // Check mChildren.isEmpty directly because hasChild() -> getChildCount() always returns 0 + if (!mChildren.isEmpty()) { + // Find the top-most root task which is a virtual child of this Tile. Because this is a + // virtual parent, the mChildren order here isn't changed during hierarchy operations. + WindowContainer parent = mChildren.get(0).getParent(); + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + if (mChildren.contains(parent.getChildAt(i))) { + top = parent.getChildAt(i); + break; + } + } + } + final Task topTask = top == null ? null : top.getTopMostTask(); + boolean isResizable = topTask == null || topTask.isResizeable(); + info.resizeMode = isResizable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; + info.topActivityType = top == null ? ACTIVITY_TYPE_UNDEFINED : top.getActivityType(); + info.configuration.setTo(getRequestedOverrideConfiguration()); + } + + @Override + void removeImmediately() { + removeAllChildren(); + super.removeImmediately(); + } + + static TaskTile forToken(IBinder token) { + try { + return (TaskTile) ((TaskToken) token).getContainer(); + } catch (ClassCastException e) { + Slog.w(TAG, "Bad tile token: " + token, e); + return null; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index d22502db69e4..4532400e3b34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -27,17 +24,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.app.Activity; -import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; @@ -46,7 +40,6 @@ import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.view.IDisplayWindowListener; -import android.view.WindowContainerTransaction; import androidx.test.filters.MediumTest; @@ -126,47 +119,6 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { } @Test - public void testTaskTransaction() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); - WindowContainerTransaction t = new WindowContainerTransaction(); - Rect newBounds = new Rect(10, 10, 100, 100); - t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); - mService.applyContainerTransaction(t); - assertEquals(newBounds, task.getBounds()); - } - - @Test - public void testStackTransaction() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - ActivityManager.StackInfo info = - mService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); - WindowContainerTransaction t = new WindowContainerTransaction(); - assertEquals(stack.mRemoteToken, info.stackToken); - Rect newBounds = new Rect(10, 10, 100, 100); - t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); - mService.applyContainerTransaction(t); - assertEquals(newBounds, stack.getBounds()); - } - - @Test - public void testContainerChanges() { - removeGlobalMinSizeRestriction(); - final ActivityStack stack = new StackBuilder(mRootWindowContainer) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); - WindowContainerTransaction t = new WindowContainerTransaction(); - assertTrue(task.isFocusable()); - t.setFocusable(stack.mRemoteToken, false); - mService.applyContainerTransaction(t); - assertFalse(task.isFocusable()); - } - - @Test public void testDisplayWindowListener() { final ArrayList<Integer> added = new ArrayList<>(); final ArrayList<Integer> changed = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 9e80cf223271..078347e96a07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 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. @@ -16,45 +16,50 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +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; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; 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 com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import android.graphics.Point; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackInfo; +import android.content.res.Configuration; import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; import android.os.Binder; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.Display; import android.view.ITaskOrganizer; +import android.view.IWindowContainer; import android.view.SurfaceControl; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import android.view.WindowContainerTransaction; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + /** - * Test class for {@link TaskOrganizer}. + * Test class for {@link ITaskOrganizer} and {@link android.app.ITaskOrganizerController}. * * Build/Install/Run: * atest WmTests:TaskOrganizerTests @@ -67,7 +72,8 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); when(organizer.asBinder()).thenReturn(new Binder()); - mWm.mAtmService.registerTaskOrganizer(organizer, windowingMode); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( + organizer, windowingMode); return organizer; } @@ -83,7 +89,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); task.removeImmediately(); verify(organizer).taskVanished(any()); @@ -97,10 +103,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); task.setTaskOrganizer(organizer2); verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any(), any()); + verify(organizer2).taskAppeared(any()); } @Test @@ -111,10 +117,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_PINNED); verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any(), any()); + verify(organizer2).taskAppeared(any()); } @Test @@ -124,7 +130,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); stack.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); + verify(organizer).taskAppeared(any()); assertTrue(stack.isControlledByTaskOrganizer()); stack.setTaskOrganizer(null); @@ -140,9 +146,176 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); final Task task2 = createTaskInStack(stack, 0 /* userId */); stack.setWindowingMode(WINDOWING_MODE_PINNED); - verify(organizer, times(1)).taskAppeared(any(), any()); + verify(organizer, times(1)).taskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); verify(organizer, times(1)).taskVanished(any()); } + + @Test + public void testTaskTransaction() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final Task task = stack.getTopMostTask(); + WindowContainerTransaction t = new WindowContainerTransaction(); + Rect newBounds = new Rect(10, 10, 100, 100); + t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertEquals(newBounds, task.getBounds()); + } + + @Test + public void testStackTransaction() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + StackInfo info = + mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertEquals(stack.mRemoteToken, info.stackToken); + Rect newBounds = new Rect(10, 10, 100, 100); + t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertEquals(newBounds, stack.getBounds()); + } + + @Test + public void testContainerChanges() { + removeGlobalMinSizeRestriction(); + final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + final Task task = stack.getTopMostTask(); + WindowContainerTransaction t = new WindowContainerTransaction(); + assertTrue(task.isFocusable()); + t.setFocusable(stack.mRemoteToken, false); + mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t); + assertFalse(task.isFocusable()); + } + + @Test + public void testCreateDeleteRootTasks() { + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + info1.configuration.windowConfiguration.getWindowingMode()); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); + + RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + info2.configuration.windowConfiguration.getWindowingMode()); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); + + DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); + List<TaskTile> infos = getTaskTiles(dc); + assertEquals(2, infos.size()); + + assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); + infos = getTaskTiles(dc); + assertEquals(1, infos.size()); + assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); + } + + @Test + public void testTileAddRemoveChild() { + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + final ActivityStack stack = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); + tile1.addChild(stack, 0 /* index */); + assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), + stack.getWindowingMode()); + + // Info should reflect new membership + List<TaskTile> tiles = getTaskTiles(mDisplayContent); + info1 = new RunningTaskInfo(); + tiles.get(0).fillTaskInfo(info1); + assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType); + + // Children inherit configuration + Rect newSize = new Rect(10, 10, 300, 300); + Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration()); + c.windowConfiguration.setBounds(newSize); + tile1.onRequestedOverrideConfigurationChanged(c); + assertEquals(newSize, stack.getBounds()); + + tile1.removeChild(stack); + assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + info1 = new RunningTaskInfo(); + tiles = getTaskTiles(mDisplayContent); + tiles.get(0).fillTaskInfo(info1); + assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); + } + + @Test + public void testTaskInfoCallback() { + final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>(); + final boolean[] called = {false}; + ITaskOrganizer listener = new ITaskOrganizer.Stub() { + @Override + public void taskAppeared(RunningTaskInfo taskInfo) { } + + @Override + public void taskVanished(IWindowContainer container) { } + + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { } + + @Override + public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { + lastReportedTiles.add(info); + called[0] = true; + } + }; + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + lastReportedTiles.clear(); + called[0] = false; + + final ActivityStack stack = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + TaskTile tile1 = TaskTile.forToken(info1.token.asBinder()); + tile1.addChild(stack, 0 /* index */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + final ActivityStack stack2 = createTaskStackOnDisplay( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + tile1.addChild(stack2, 0 /* index */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + mDisplayContent.positionStackAtTop(stack, false /* includingParents */); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); + + lastReportedTiles.clear(); + called[0] = false; + tile1.removeAllChildren(); + assertTrue(called[0]); + assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); + } + + private List<TaskTile> getTaskTiles(DisplayContent dc) { + ArrayList<TaskTile> out = new ArrayList<>(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final Task t = dc.getStackAt(i); + if (t instanceof TaskTile) { + out.add((TaskTile) t); + } + } + return out; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 8e362ae4c59a..1ca2e318b0d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -40,6 +40,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.mockito.Mockito.mock; import android.content.Context; +import android.content.Intent; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; @@ -340,6 +341,7 @@ class WindowTestsBase extends SystemServiceTestsBase { .setWindowingMode(windowingMode) .setActivityType(activityType) .setCreateActivity(false) + .setIntent(new Intent()) .build(); } } diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java index bc8d3c349f9d..3fb0050d0c55 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; +import android.os.RemoteException; import android.view.ITaskOrganizer; import android.view.IWindowContainer; import android.view.SurfaceControl; @@ -47,13 +48,16 @@ public class TaskOrganizerMultiWindowTest extends Activity { class Organizer extends ITaskOrganizer.Stub { @Override - public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { - mView.reparentTask(wc); + public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + mView.reparentTask(ti.token); } public void taskVanished(IWindowContainer wc) { } public void transactionReady(int id, SurfaceControl.Transaction t) { } + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + } } Organizer mOrganizer = new Organizer(); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index fc1be28d2b8f..8f3cb3442f5a 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -21,18 +21,14 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.Service; -import android.app.WindowConfiguration; -import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.IBinder; import android.view.ITaskOrganizer; import android.view.IWindowContainer; -import android.view.WindowContainerTransaction; import android.view.SurfaceControl; -import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.ViewGroup; +import android.view.WindowContainerTransaction; import android.view.WindowManager; import android.widget.FrameLayout; @@ -43,13 +39,13 @@ public class TaskOrganizerPipTest extends Service { TaskView mTaskView; class Organizer extends ITaskOrganizer.Stub { - public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { - mTaskView.reparentTask(wc); + public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + mTaskView.reparentTask(ti.token); final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); try { - ActivityTaskManager.getService().applyContainerTransaction(wct); + ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct); } catch (Exception e) { } } @@ -57,6 +53,8 @@ public class TaskOrganizerPipTest extends Service { } public void transactionReady(int id, SurfaceControl.Transaction t) { } + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + } } Organizer mOrganizer = new Organizer(); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java index ff73340fc947..9f32bb8a0bf7 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java @@ -44,7 +44,7 @@ class TaskView extends SurfaceView implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder holder) { try { - ActivityTaskManager.getService().registerTaskOrganizer(mTaskOrganizer, + ActivityTaskManager.getTaskOrganizerController().registerTaskOrganizer(mTaskOrganizer, mWindowingMode); } catch (Exception e) { } |