From 2d81132f30d220d03d1f0f7daac57120db2c66c6 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Sun, 18 Oct 2020 16:18:23 +0100 Subject: [PATCH] Move letterbox task positioning to LetterboxTaskListener. Changes: - Introduce LetterboxTaskListerner - Pass activity bounds and task position from WM Core to WM Shell - Position a task from LetterboxTaskListener. Fix: 170210431, 170213038, 168706428, 171549361 Test: go/wm-smoke Test: atest ShellTaskOrganizerTests, LetterboxTaskListenerTest Change-Id: I9c25b7fe1053900fe3b6eea8e71db302a0c14a5a --- core/java/android/app/TaskInfo.java | 25 +++- .../Shell/res/raw/wm_shell_protolog.json | 18 +++ .../wm/shell/FullscreenTaskListener.java | 39 +----- .../wm/shell/LetterboxTaskListener.java | 110 +++++++++++++++ .../android/wm/shell/ShellTaskOrganizer.java | 18 ++- .../wm/shell/LetterboxTaskListenerTest.java | 125 ++++++++++++++++++ .../wm/shell/ShellTaskOrganizerTests.java | 44 +++++- .../com/android/server/wm/ActivityRecord.java | 7 + .../core/java/com/android/server/wm/Task.java | 32 +++++ .../server/wm/TaskOrganizerController.java | 7 + 10 files changed, 381 insertions(+), 44 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 849f679c94390..f1a67767cbe65 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -24,6 +24,8 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; @@ -180,6 +182,20 @@ public class TaskInfo { */ public boolean isResizeable; + /** + * Activity bounds if this task or its top activity is presented in letterbox mode and + * {@code null} otherwise. + * @hide + */ + @Nullable + public Rect letterboxActivityBounds; + + /** + * Relative position of the task's top left corner in the parent container. + * @hide + */ + public Point positionInParent; + /** * The launch cookies associated with activities in this task if any. * @see ActivityOptions#setLaunchCookie(IBinder) @@ -256,6 +272,8 @@ public class TaskInfo { topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); source.readBinderList(launchCookies); + letterboxActivityBounds = source.readTypedObject(Rect.CREATOR); + positionInParent = source.readTypedObject(Point.CREATOR); } /** @@ -287,6 +305,8 @@ public class TaskInfo { dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); dest.writeBinderList(launchCookies); + dest.writeTypedObject(letterboxActivityBounds, flags); + dest.writeTypedObject(positionInParent, flags); } @Override @@ -306,6 +326,9 @@ public class TaskInfo { + " topActivityType=" + topActivityType + " pictureInPictureParams=" + pictureInPictureParams + " topActivityInfo=" + topActivityInfo - + " launchCookies" + launchCookies; + + " launchCookies" + launchCookies + + " letterboxActivityBounds=" + letterboxActivityBounds + + " positionInParent=" + positionInParent + + "}"; } } diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index f8db4477d87ac..358d440686dd7 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -55,6 +55,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "-848099324": { + "message": "Letterbox Task Appeared: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, "-712674749": { "message": "Clip description: %s", "level": "VERBOSE", @@ -115,12 +121,24 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "1104702476": { + "message": "Letterbox Task Changed: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, "1184615936": { "message": "Set drop target window visibility: displayId=%d visibility=%d", "level": "VERBOSE", "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java" }, + "1218010718": { + "message": "Letterbox Task Vanished: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" + }, "1481772149": { "message": "Current target: %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index 5bd693a9311ec..fc0a76e8d2866 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -20,9 +20,7 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCR import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; import android.app.ActivityManager; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.view.SurfaceControl; @@ -39,7 +37,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private final SyncTransactionQueue mSyncQueue; - private final ArrayMap mTasks = new ArrayMap<>(); + private final ArraySet mTasks = new ArraySet<>(); FullscreenTaskListener(SyncTransactionQueue syncQueue) { mSyncQueue = syncQueue; @@ -48,17 +46,17 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mTasks) { - if (mTasks.containsKey(taskInfo.taskId)) { + if (mTasks.contains(taskInfo.taskId)) { throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); - mTasks.put(taskInfo.taskId, leash); + mTasks.add(taskInfo.taskId); mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). - updateSurfacePosition(t, taskInfo, leash); t.setWindowCrop(leash, null); + t.setPosition(leash, 0, 0); // TODO(shell-transitions): Eventually set everything in transition so there's no // SF Transaction here. if (!Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -73,7 +71,7 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { synchronized (mTasks) { - if (mTasks.remove(taskInfo.taskId) == null) { + if (!mTasks.remove(taskInfo.taskId)) { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; } @@ -82,23 +80,6 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } } - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mTasks) { - if (!mTasks.containsKey(taskInfo.taskId)) { - Slog.e(TAG, "Changed Task wasn't appeared or already vanished: #" - + taskInfo.taskId); - return; - } - final SurfaceControl leash = mTasks.get(taskInfo.taskId); - mSyncQueue.runInSync(t -> { - // Reposition the task in case the bounds has been changed (such as Task level - // letterboxing). - updateSurfacePosition(t, taskInfo, leash); - }); - } - } - @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; @@ -112,12 +93,4 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - /** Places the Task surface to the latest position. */ - private static void updateSurfacePosition(SurfaceControl.Transaction t, - ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - // TODO(170725334) drop this after ag/12876439 - final Configuration config = taskInfo.getConfiguration(); - final Rect bounds = config.windowConfiguration.getBounds(); - t.setPosition(leash, bounds.left, bounds.top); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java new file mode 100644 index 0000000000000..9010c2088c341 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java @@ -0,0 +1,110 @@ +/* + * 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.wm.shell; + +import android.app.ActivityManager; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Slog; +import android.util.SparseArray; +import android.view.SurfaceControl; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when + * it's presented in the letterbox mode either because orientations of a top activity and a device + * don't match or because a top activity is in a size compat mode. + */ +final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "LetterboxTaskListener"; + + private final SyncTransactionQueue mSyncQueue; + + private final SparseArray mLeashByTaskId = new SparseArray<>(); + + LetterboxTaskListener(SyncTransactionQueue syncQueue) { + mSyncQueue = syncQueue; + } + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + synchronized (mLeashByTaskId) { + if (mLeashByTaskId.get(taskInfo.taskId) != null) { + throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d", + taskInfo.taskId); + mLeashByTaskId.put(taskInfo.taskId, leash); + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Rect activtyBounds = taskInfo.letterboxActivityBounds; + final Point taskPositionInParent = taskInfo.positionInParent; + mSyncQueue.runInSync(t -> { + setPositionAndWindowCrop( + t, leash, activtyBounds, taskBounds, taskPositionInParent); + if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } + }); + } + } + + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + synchronized (mLeashByTaskId) { + if (mLeashByTaskId.get(taskInfo.taskId) == null) { + Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + return; + } + mLeashByTaskId.remove(taskInfo.taskId); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d", + taskInfo.taskId); + } + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + synchronized (mLeashByTaskId) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d", + taskInfo.taskId); + final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Rect activtyBounds = taskInfo.letterboxActivityBounds; + final Point taskPositionInParent = taskInfo.positionInParent; + mSyncQueue.runInSync(t -> { + setPositionAndWindowCrop( + t, leash, activtyBounds, taskBounds, taskPositionInParent); + }); + } + } + + private static void setPositionAndWindowCrop( + SurfaceControl.Transaction transaction, + SurfaceControl leash, + final Rect activityBounds, + final Rect taskBounds, + final Point taskPositionInParent) { + Rect activtyInTaskCoordinates = new Rect(activityBounds); + activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top); + transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); + transaction.setWindowCrop(leash, activtyInTaskCoordinates); + } +} 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 cbc1c8d6d3102..5234fa0df77f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -45,7 +45,6 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; import java.util.ArrayList; @@ -65,6 +64,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3; public static final int TASK_LISTENER_TYPE_PIP = -4; public static final int TASK_LISTENER_TYPE_SPLIT_SCREEN = -5; + public static final int TASK_LISTENER_TYPE_LETTERBOX = -6; @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = { TASK_LISTENER_TYPE_UNDEFINED, @@ -72,6 +72,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_PIP, TASK_LISTENER_TYPE_SPLIT_SCREEN, + TASK_LISTENER_TYPE_LETTERBOX, }) public @interface TaskListenerType {} @@ -118,6 +119,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(taskOrganizerController, mainExecutor); addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN); + addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX); mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } @@ -329,8 +331,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (listener != null) return listener; // Next we try type specific listeners. - final int windowingMode = getWindowingMode(runningTaskInfo); - final int taskListenerType = windowingModeToTaskListenerType(windowingMode); + final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo); return mTaskListeners.get(taskListenerType); } @@ -339,11 +340,14 @@ public class ShellTaskOrganizer extends TaskOrganizer { return taskInfo.configuration.windowConfiguration.getWindowingMode(); } - private static @TaskListenerType int windowingModeToTaskListenerType( - @WindowingMode int windowingMode) { + @VisibleForTesting + static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) { + final int windowingMode = getWindowingMode(runningTaskInfo); switch (windowingMode) { case WINDOWING_MODE_FULLSCREEN: - return TASK_LISTENER_TYPE_FULLSCREEN; + return runningTaskInfo.letterboxActivityBounds != null + ? TASK_LISTENER_TYPE_LETTERBOX + : TASK_LISTENER_TYPE_FULLSCREEN; case WINDOWING_MODE_MULTI_WINDOW: return TASK_LISTENER_TYPE_MULTI_WINDOW; case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: @@ -362,6 +366,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { switch (type) { case TASK_LISTENER_TYPE_FULLSCREEN: return "TASK_LISTENER_TYPE_FULLSCREEN"; + case TASK_LISTENER_TYPE_LETTERBOX: + return "TASK_LISTENER_TYPE_LETTERBOX"; case TASK_LISTENER_TYPE_MULTI_WINDOW: return "TASK_LISTENER_TYPE_MULTI_WINDOW"; case TASK_LISTENER_TYPE_SPLIT_SCREEN: diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java new file mode 100644 index 0000000000000..45d4d5d347dd6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java @@ -0,0 +1,125 @@ +/* + * 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.wm.shell; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.mockito.ArgumentMatchers.eq; + +import android.app.ActivityManager.RunningTaskInfo; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.view.SurfaceControl; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link LetterboxTaskListener}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LetterboxTaskListenerTest { + + private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400); + private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500); + private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500); + private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50); + private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100); + + private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300); + private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200); + + private static final RunningTaskInfo TASK_INFO = createTaskInfo( + /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT); + + private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo( + /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2); + + @Mock private SurfaceControl mLeash; + @Mock private SurfaceControl.Transaction mTransaction; + private LetterboxTaskListener mLetterboxTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLetterboxTaskListener = new LetterboxTaskListener( + new SyncTransactionQueue( + new TransactionPool() { + @Override + public SurfaceControl.Transaction acquire() { + return mTransaction; + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + }, + new Handler(Looper.getMainLooper()))); + } + + @Test + public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() { + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + + verify(mTransaction).setPosition( + eq(mLeash), + eq((float) TASK_POSITION_IN_PARENT.x), + eq((float) TASK_POSITION_IN_PARENT.y)); + // Should return activty coordinates offset by task coordinates + verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP)); + + mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2); + + verify(mTransaction).setPosition( + eq(mLeash), + eq((float) TASK_POSITION_IN_PARENT_2.x), + eq((float) TASK_POSITION_IN_PARENT_2.y)); + // Should return activty coordinates offset by task coordinates + verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2)); + } + + @Test(expected = RuntimeException.class) + public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() { + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); + } + + private static RunningTaskInfo createTaskInfo( + int taskId, + final Rect activityBounds, + final Rect taskBounds, + final Point taskPositionInParent) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setBounds(taskBounds); + taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds); + taskInfo.positionInParent = new Point(taskPositionInParent); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 07a6bda239c79..35a2293cbf130 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -16,15 +16,18 @@ package com.android.wm.shell; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; - +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,6 +37,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -42,6 +46,7 @@ import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; import android.window.TaskAppearedInfo; +import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -242,10 +247,41 @@ public class ShellTaskOrganizerTests { assertTrue(gotException); } - private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { + @Test + public void testTaskInfoToTaskListenerType_whenLetterboxBoundsPassed_returnsLetterboxType() { + RunningTaskInfo taskInfo = createTaskInfo( + /* taskId */ 1, + WINDOWING_MODE_FULLSCREEN, + /* letterboxActivityBounds */ new Rect(1, 1, 1, 1)); + + assertEquals( + ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo), + TASK_LISTENER_TYPE_LETTERBOX); + } + + @Test + public void testTaskInfoToTaskListenerType_whenLetterboxBoundsIsNull_returnsFullscreenType() { + RunningTaskInfo taskInfo = createTaskInfo( + /* taskId */ 1, WINDOWING_MODE_FULLSCREEN, /* letterboxActivityBounds */ null); + + assertEquals( + ShellTaskOrganizer.taskInfoToTaskListenerType(taskInfo), + TASK_LISTENER_TYPE_FULLSCREEN); + } + + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); return taskInfo; } + + private static RunningTaskInfo createTaskInfo( + int taskId, int windowingMode, @Nullable Rect letterboxActivityBounds) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + taskInfo.letterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds); + return taskInfo; + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 13e23f766668c..6de5522ee6946 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1364,6 +1364,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLetterbox != null) { mLetterbox.hide(); } + task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w)); } void updateLetterboxSurface(WindowState winHint) { @@ -1377,6 +1378,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Nullable + private Rect getLetterboxParams(WindowState w) { + boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent(); + return isLetterboxed ? getBounds() : null; + } + Rect getLetterboxInsets() { if (mLetterbox != null) { return mLetterbox.getInsets(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 557c92e9704e3..6073ca5b0a4c9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -528,6 +528,11 @@ class Task extends WindowContainer { // {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root activity. boolean mSupportsPictureInPicture; + // Activity bounds if this task or its top activity is presented in letterbox mode and + // {@code null} otherwise. + @Nullable + private Rect mLetterboxActivityBounds; + // Whether the task is currently being drag-resized private boolean mDragResizing; private int mDragResizeMode; @@ -4058,6 +4063,12 @@ class Task extends WindowContainer { info.resizeMode = top != null ? top.mResizeMode : mResizeMode; info.topActivityType = top.getActivityType(); info.isResizeable = isResizeable(); + // Don't query getTopNonFinishingActivity().getBounds() directly because when fillTaskInfo + // is triggered for the first time after activities change, getBounds() may return non final + // bounds, e.g. fullscreen bounds instead of letterboxed bounds. To work around this, + // assigning bounds from ActivityRecord#layoutLetterbox when they are ready. + info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds); + info.positionInParent = getRelativePosition(); info.pictureInPictureParams = getPictureInPictureParams(); info.topActivityInfo = mReuseActivitiesReport.top != null @@ -4076,6 +4087,21 @@ class Task extends WindowContainer { ? null : rootActivity.pictureInPictureArgs; } + void maybeUpdateLetterboxBounds( + ActivityRecord activityRecord, @Nullable Rect letterboxActivityBounds) { + if (isOrganized() + && mReuseActivitiesReport.top == activityRecord + // Want to force update only if letterbox bounds have changed. + && !Objects.equals( + mLetterboxActivityBounds, + letterboxActivityBounds)) { + mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds); + // Forcing update to reduce visual jank during the transition. + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, /* force= */ true); + } + } + /** * Returns a {@link TaskInfo} with information from this task. */ @@ -7453,6 +7479,12 @@ class Task extends WindowContainer { outPos.y -= outset; } + private Point getRelativePosition() { + Point position = new Point(); + getRelativePosition(position); + return position; + } + boolean shouldIgnoreInput() { if (inSplitScreenPrimaryWindowingMode() && !isFocusable()) { return true; diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 6504f00905e73..5f86c2f60db74 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -53,6 +53,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Consumer; @@ -475,6 +476,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean changed = lastInfo == null || mTmpTaskInfo.topActivityType != lastInfo.topActivityType || mTmpTaskInfo.isResizeable != lastInfo.isResizeable + || !Objects.equals( + mTmpTaskInfo.letterboxActivityBounds, + lastInfo.letterboxActivityBounds) + || !Objects.equals( + mTmpTaskInfo.positionInParent, + lastInfo.positionInParent) || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams || mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode() != lastInfo.getConfiguration().windowConfiguration.getWindowingMode()