diff options
7 files changed, 260 insertions, 34 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b5409b7d78af..43679364b443 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -876,8 +876,12 @@ public class ShellTaskOrganizer extends TaskOrganizer implements pkg = info.getTaskInfo().baseActivity.getPackageName(); } Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds(); + boolean running = info.getTaskInfo().isRunning; + boolean visible = info.getTaskInfo().isVisible; + boolean focused = info.getTaskInfo().isFocused; pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener - + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds); + + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds + + " running=" + running + " visible=" + visible + " focused=" + focused); } pw.println(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index b7656dec5d6a..7c50982b7b86 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -220,13 +220,14 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, + Optional<RecentTasksController> recentTasksController, WindowDecorViewModel<?> windowDecorViewModel) { // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module ShellInit init = FreeformComponents.isFreeformEnabled(context) ? shellInit : null; - return new FreeformTaskListener<>(init, shellTaskOrganizer, + return new FreeformTaskListener<>(init, shellTaskOrganizer, recentTasksController, windowDecorViewModel); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index 64cec2a270ea..8993d549964c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -50,7 +50,7 @@ public class DesktopMode { Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result); return result != 0; - } catch (Settings.SettingNotFoundException e) { + } catch (Exception e) { ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e); return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 8dcdda1895e6..1baac718ee95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -28,12 +28,15 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; +import java.util.Optional; /** * {@link ShellTaskOrganizer.TaskListener} for {@link @@ -46,6 +49,7 @@ public class FreeformTaskListener<T extends AutoCloseable> private static final String TAG = "FreeformTaskListener"; private final ShellTaskOrganizer mShellTaskOrganizer; + private final Optional<RecentTasksController> mRecentTasksOptional; private final WindowDecorViewModel<T> mWindowDecorationViewModel; private final SparseArray<State<T>> mTasks = new SparseArray<>(); @@ -60,9 +64,11 @@ public class FreeformTaskListener<T extends AutoCloseable> public FreeformTaskListener( ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, + Optional<RecentTasksController> recentTasksController, WindowDecorViewModel<T> windowDecorationViewModel) { mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; + mRecentTasksOptional = recentTasksController; if (shellInit != null) { shellInit.addInitCallback(this::onInit, this); } @@ -83,6 +89,12 @@ public class FreeformTaskListener<T extends AutoCloseable> mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t); t.apply(); } + + if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Adding active freeform task: #%d", taskInfo.taskId); + mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + } } private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) { @@ -111,6 +123,12 @@ public class FreeformTaskListener<T extends AutoCloseable> taskInfo.taskId); mTasks.remove(taskInfo.taskId); + if (DesktopMode.IS_SUPPORTED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Removing active freeform task: #%d", taskInfo.taskId); + mRecentTasksOptional.ifPresent(rt -> rt.removeActiveFreeformTask(taskInfo.taskId)); + } + if (Transitions.ENABLE_SHELL_TRANSITIONS) { // Save window decorations of closing tasks so that we can hand them over to the // transition system if this method happens before the transition. In case where the @@ -131,6 +149,14 @@ public class FreeformTaskListener<T extends AutoCloseable> if (state.mWindowDecoration != null) { mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration); } + + if (DesktopMode.IS_SUPPORTED) { + if (taskInfo.isVisible) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Adding active freeform task: #%d", taskInfo.taskId); + mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + } + } } private State<T> updateTaskInfo(RunningTaskInfo taskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 7b42350b1365..27bc1a189086 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -43,6 +43,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -52,6 +53,7 @@ import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -82,6 +84,15 @@ public class RecentTasksController implements TaskStackListenerCallback, private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** + * Set of taskId's that have been launched in freeform mode. + * This includes tasks that are currently running, visible and in freeform mode. And also + * includes tasks that are running in the background, are no longer visible, but at some point + * were visible to the user. + * This is used to decide which freeform apps belong to the user's desktop. + */ + private final HashSet<Integer> mActiveFreeformTasks = new HashSet<>(); + + /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not * supported. */ @@ -206,6 +217,22 @@ public class RecentTasksController implements TaskStackListenerCallback, notifyRecentTasksChanged(); } + /** + * Mark a task with given {@code taskId} as active in freeform + */ + public void addActiveFreeformTask(int taskId) { + mActiveFreeformTasks.add(taskId); + notifyRecentTasksChanged(); + } + + /** + * Remove task with given {@code taskId} from active freeform tasks + */ + public void removeActiveFreeformTask(int taskId) { + mActiveFreeformTasks.remove(taskId); + notifyRecentTasksChanged(); + } + @VisibleForTesting void notifyRecentTasksChanged() { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Notify recent tasks changed"); @@ -273,6 +300,9 @@ public class RecentTasksController implements TaskStackListenerCallback, rawMapping.put(taskInfo.taskId, taskInfo); } + boolean desktopModeActive = DesktopMode.isActive(mContext); + ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + // Pull out the pairs as we iterate back in the list ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); for (int i = 0; i < rawList.size(); i++) { @@ -282,16 +312,31 @@ public class RecentTasksController implements TaskStackListenerCallback, continue; } + if (desktopModeActive && mActiveFreeformTasks.contains(taskInfo.taskId)) { + // Freeform tasks will be added as a separate entry + freeformTasks.add(taskInfo); + continue; + } + final int pairedTaskId = mSplitTasks.get(taskInfo.taskId); - if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) { + if (!desktopModeActive && pairedTaskId != INVALID_TASK_ID && rawMapping.contains( + pairedTaskId)) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, + recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); + recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo)); } } + + // Add a special entry for freeform tasks + if (!freeformTasks.isEmpty()) { + // First task is added separately + recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks( + freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]))); + } + return recentTasks; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java index 2cff1714aff6..eab75b983268 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -16,6 +16,7 @@ package com.android.wm.shell.util; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.os.Parcel; @@ -24,40 +25,142 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + /** * Simple container for recent tasks. May contain either a single or pair of tasks. */ public class GroupedRecentTaskInfo implements Parcelable { - public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; - public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; - public @Nullable SplitBounds mSplitBounds; - public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { - this(task1, null, null); + public static final int TYPE_SINGLE = 1; + public static final int TYPE_SPLIT = 2; + public static final int TYPE_FREEFORM = 3; + + @IntDef(prefix = {"TYPE_"}, value = { + TYPE_SINGLE, + TYPE_SPLIT, + TYPE_FREEFORM + }) + public @interface GroupType {} + + @NonNull + private final ActivityManager.RecentTaskInfo[] mTasks; + @Nullable + private final SplitBounds mSplitBounds; + @GroupType + private final int mType; + + /** + * Create new for a single task + */ + public static GroupedRecentTaskInfo forSingleTask( + @NonNull ActivityManager.RecentTaskInfo task) { + return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, + TYPE_SINGLE); + } + + /** + * Create new for a pair of tasks in split screen + */ + public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, + @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { + return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, + splitBounds, TYPE_SPLIT); } - public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, - @Nullable ActivityManager.RecentTaskInfo task2, - @Nullable SplitBounds splitBounds) { - mTaskInfo1 = task1; - mTaskInfo2 = task2; + /** + * Create new for a group of freeform tasks + */ + public static GroupedRecentTaskInfo forFreeformTasks( + @NonNull ActivityManager.RecentTaskInfo... tasks) { + return new GroupedRecentTaskInfo(tasks, null, TYPE_FREEFORM); + } + + private GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo[] tasks, + @Nullable SplitBounds splitBounds, @GroupType int type) { + mTasks = tasks; mSplitBounds = splitBounds; + mType = type; } GroupedRecentTaskInfo(Parcel parcel) { - mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); - mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); + mType = parcel.readInt(); + } + + /** + * Get primary {@link ActivityManager.RecentTaskInfo} + */ + @NonNull + public ActivityManager.RecentTaskInfo getTaskInfo1() { + return mTasks[0]; + } + + /** + * Get secondary {@link ActivityManager.RecentTaskInfo}. + * + * Used in split screen. + */ + @Nullable + public ActivityManager.RecentTaskInfo getTaskInfo2() { + if (mTasks.length > 1) { + return mTasks[1]; + } + return null; + } + + /** + * Get all {@link ActivityManager.RecentTaskInfo}s grouped together. + */ + public List<ActivityManager.RecentTaskInfo> getAllTaskInfos() { + return Arrays.asList(mTasks); + } + + /** + * Return {@link SplitBounds} if this is a split screen entry or {@code null} + */ + @Nullable + public SplitBounds getSplitBounds() { + return mSplitBounds; + } + + /** + * Get type of this recents entry. One of {@link GroupType} + */ + @GroupType + public int getType() { + return mType; } @Override public String toString() { - String taskString = "Task1: " + getTaskInfo(mTaskInfo1) - + ", Task2: " + getTaskInfo(mTaskInfo2); + StringBuilder taskString = new StringBuilder(); + for (int i = 0; i < mTasks.length; i++) { + if (i == 0) { + taskString.append("Task"); + } else { + taskString.append(", Task"); + } + taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i])); + } if (mSplitBounds != null) { - taskString += ", SplitBounds: " + mSplitBounds.toString(); + taskString.append(", SplitBounds: ").append(mSplitBounds); + } + taskString.append(", Type=").append(mType); + switch (mType) { + case TYPE_SINGLE: + taskString.append("TYPE_SINGLE"); + break; + case TYPE_SPLIT: + taskString.append("TYPE_SPLIT"); + break; + case TYPE_FREEFORM: + taskString.append("TYPE_FREEFORM"); + break; } - return taskString; + return taskString.toString(); } private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { @@ -74,9 +177,9 @@ public class GroupedRecentTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedObject(mTaskInfo1, flags); - parcel.writeTypedObject(mTaskInfo2, flags); + parcel.writeTypedArray(mTasks, flags); parcel.writeTypedObject(mSplitBounds, flags); + parcel.writeInt(mType); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 81bb609cc711..9e755dca7908 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -20,6 +20,9 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -45,11 +48,13 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -179,6 +184,46 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + public void testGetRecentTasks_groupActiveFreeformTasks() { + StaticMockitoSession mockitoSession = mockitoSession().mockStatic( + DesktopMode.class).startMocking(); + when(DesktopMode.isActive(any())).thenReturn(true); + + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + setRawList(t1, t2, t3, t4); + + mRecentTasksController.addActiveFreeformTask(1); + mRecentTasksController.addActiveFreeformTask(3); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + + // 2 freeform tasks should be grouped into one, 3 total recents entries + assertEquals(3, recentTasks.size()); + GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); + GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + + // Check that groups have expected types + assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); + assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + + // Check freeform group entries + assertEquals(t1, freeformGroup.getAllTaskInfos().get(0)); + assertEquals(t3, freeformGroup.getAllTaskInfos().get(1)); + + // Check single entries + assertEquals(t2, singleGroup1.getTaskInfo1()); + assertEquals(t4, singleGroup2.getTaskInfo1()); + + mockitoSession.finishMocking(); + } + + @Test public void testRemovedTaskRemovesSplit() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); @@ -254,6 +299,7 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Asserts that the recent tasks matches the given task ids. + * * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in * the grouped task list */ @@ -262,22 +308,23 @@ public class RecentTasksControllerTest extends ShellTestCase { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { GroupedRecentTaskInfo pair = recentTasks.get(i); - int taskId1 = pair.mTaskInfo1.taskId; + int taskId1 = pair.getTaskInfo1().taskId; flattenedTaskIds[2 * i] = taskId1; - flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null - ? pair.mTaskInfo2.taskId + flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null + ? pair.getTaskInfo2().taskId : -1; - if (pair.mTaskInfo2 != null) { - assertNotNull(pair.mSplitBounds); - int leftTopTaskId = pair.mSplitBounds.leftTopTaskId; - int bottomRightTaskId = pair.mSplitBounds.rightBottomTaskId; + if (pair.getTaskInfo2() != null) { + assertNotNull(pair.getSplitBounds()); + int leftTopTaskId = pair.getSplitBounds().leftTopTaskId; + int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId; // Unclear if pairs are ordered by split position, most likely not. - assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); + assertTrue(leftTopTaskId == taskId1 + || leftTopTaskId == pair.getTaskInfo2().taskId); assertTrue(bottomRightTaskId == taskId1 - || bottomRightTaskId == pair.mTaskInfo2.taskId); + || bottomRightTaskId == pair.getTaskInfo2().taskId); } else { - assertNull(pair.mSplitBounds); + assertNull(pair.getSplitBounds()); } } assertTrue("Expected: " + Arrays.toString(expectedTaskIds) |