diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index e31e0243cae06..390d9219ef2ab 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -336,6 +336,23 @@ public class TaskInfo { && isVisible == that.isVisible; } + /** + * @return {@code true} if parameters that are important for size compat have changed. + * @hide + */ + public boolean equalsForSizeCompat(@Nullable TaskInfo that) { + if (that == null) { + return false; + } + return displayId == that.displayId + && taskId == that.taskId + && topActivityInSizeCompat == that.topActivityInSizeCompat + // TopActivityToken and bounds are important if top activity is in size compat + && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken)) + && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds() + .equals(that.configuration.windowConfiguration.getBounds())); + } + /** * Reads the TaskInfo from a parcel. */ 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 4b3fc811f7eb9..afe523af7cb02 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -106,5 +106,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { public String toString() { return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - } 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 c9b38d00c0ae4..a570c0af698de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -44,6 +44,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sizecompatui.SizeCompatUI; import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import java.io.PrintWriter; @@ -102,18 +103,31 @@ public class ShellTaskOrganizer extends TaskOrganizer { private final Object mLock = new Object(); private final StartingSurfaceDrawer mStartingSurfaceDrawer; + /** + * In charge of showing size compat UI. Can be {@code null} if device doesn't support size + * compat. + */ + @Nullable + private final SizeCompatUI mSizeCompatUI; + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null, mainExecutor, context); + this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); + } + + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable + SizeCompatUI sizeCompatUI) { + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context) { + Context context, @Nullable SizeCompatUI sizeCompatUI) { super(taskOrganizerController, mainExecutor); // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled // by a controller, that class should be create while porting // ActivityRecord#addStartingWindow to WMShell. mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor); + mSizeCompatUI = sizeCompatUI; } @Override @@ -255,6 +269,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (listener != null) { listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } + notifySizeCompatUI(info.getTaskInfo(), listener); } @Override @@ -270,6 +285,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (!updated && newListener != null) { newListener.onTaskInfoChanged(taskInfo); } + if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) { + // Notify the size compat UI if the listener or task info changed. + notifySizeCompatUI(taskInfo, newListener); + } } } @@ -294,6 +313,8 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (listener != null) { listener.onTaskVanished(taskInfo); } + // Pass null for listener to remove the size compat UI on this task if there is any. + notifySizeCompatUI(taskInfo, null /* taskListener */); } } @@ -320,6 +341,34 @@ public class ShellTaskOrganizer extends TaskOrganizer { return true; } + /** + * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update + * the UI accordingly. + * + * @param taskInfo the new Task info + * @param taskListener listener to handle the Task Surface placement. {@code null} if task is + * vanished. + */ + private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) { + if (mSizeCompatUI == null) { + return; + } + + // The task is vanished, notify to remove size compat UI on this Task if there is any. + if (taskListener == null) { + mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, + null /* taskConfig */, null /* sizeCompatActivity*/, + null /* taskListener */); + return; + } + + mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, + taskInfo.configuration.windowConfiguration.getBounds(), + // null if the top activity not in size compat. + taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null, + taskListener); + } + private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) { return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index a2cd683f2fdd5..b2ac61cf3f6ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -159,6 +159,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } + private void dispatchVisibilityChanged(int displayId, boolean isShowing) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImeVisibilityChanged(displayId, isShowing); + } + } + } + /** * Adds an {@link ImePositionProcessor} to be called during ime position updates. */ @@ -212,7 +220,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return; } - mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME); + updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)); final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME); final Rect newFrame = newSource.getFrame(); @@ -371,7 +379,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged seek = true; } mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; - mImeShowing = show; + updateImeVisibility(show); mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); @@ -455,6 +463,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } + private void updateImeVisibility(boolean isShowing) { + if (mImeShowing != isShowing) { + mImeShowing = isShowing; + dispatchVisibilityChanged(mDisplayId, isShowing); + } + } + @VisibleForTesting @BinderThread public class DisplayWindowInsetsControllerImpl @@ -563,6 +578,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged default void onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) { } + + /** + * Called when the IME visibility changed. + * + * @param isShowing {@code true} if the IME is shown. + */ + default void onImeVisibilityChanged(int displayId, boolean isShowing) { + + } } public IInputMethodManager getImms() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java new file mode 100644 index 0000000000000..11f22ed24a690 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.sizecompatui; + +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.IBinder; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.annotations.ExternalThread; + +/** + * Interface to engage size compat mode UI. + */ +@ExternalThread +public interface SizeCompatUI { + /** + * Called when the Task info changed. Creates and updates the restart button if there is an + * activity in size compat, or removes the restart button if there is no size compat activity. + * + * @param displayId display the task and activity are in. + * @param taskId task the activity is in. + * @param taskBounds task bounds to place the restart button in. + * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the + * top activity in this Task is not in size compat. + * @param taskListener listener to handle the Task Surface placement. + */ + void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, + @Nullable IBinder sizeCompatActivity, + @Nullable ShellTaskOrganizer.TaskListener taskListener); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java new file mode 100644 index 0000000000000..44f391c953a09 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 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.sizecompatui; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.ShellExecutor; + +/** + * Shows a restart-activity button on Task when the foreground activity is in size compatibility + * mode. + */ +public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener, + DisplayImeController.ImePositionProcessor { + private static final String TAG = "SizeCompatUI"; + + @VisibleForTesting + final SizeCompatUI mImpl = new SizeCompatUIImpl(); + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final DisplayController mDisplayController; + private final DisplayImeController mImeController; + + /** Creates the {@link SizeCompatUIController}. */ + public static SizeCompatUI create(Context context, + DisplayController displayController, + DisplayImeController imeController, + ShellExecutor mainExecutor) { + return new SizeCompatUIController(context, displayController, imeController, mainExecutor) + .mImpl; + } + + @VisibleForTesting + SizeCompatUIController(Context context, + DisplayController displayController, + DisplayImeController imeController, + ShellExecutor mainExecutor) { + mContext = context; + mMainExecutor = mainExecutor; + mDisplayController = displayController; + mImeController = imeController; + mDisplayController.addDisplayWindowListener(this); + mImeController.addPositionProcessor(this); + } + + private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, + @Nullable IBinder sizeCompatActivity, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + // TODO need to deduplicate task info changed + } + + // TODO move from SizeCompatModeActivityController from system UI. + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onImeVisibilityChanged(int displayId, boolean isShowing) { + } + + private class SizeCompatUIImpl implements SizeCompatUI { + @Override + public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds, + @Nullable IBinder sizeCompatActivity, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + mMainExecutor.execute(() -> + SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId, + taskBounds, sizeCompatActivity, taskListener)); + } + } +} 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 862776ec7df22..80ea9b9e177ec 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,11 +16,14 @@ 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 android.view.Display.DEFAULT_DISPLAY; 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_MULTI_WINDOW; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; @@ -48,6 +51,7 @@ import androidx.test.filters.SmallTest; 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.sizecompatui.SizeCompatUI; import org.junit.Before; import org.junit.Test; @@ -59,6 +63,9 @@ import java.util.ArrayList; /** * Tests for the shell task organizer. + * + * Build/Install/Run: + * atest WMShellUnitTests:ShellTaskOrganizerTests */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -68,6 +75,8 @@ public class ShellTaskOrganizerTests { private ITaskOrganizerController mTaskOrganizerController; @Mock private Context mContext; + @Mock + private SizeCompatUI mSizeCompatUI; ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); @@ -102,7 +111,8 @@ public class ShellTaskOrganizerTests { doReturn(ParceledListSlice.emptyList()) .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} - mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext)); + mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, + mSizeCompatUI)); } @Test @@ -257,6 +267,39 @@ public class ShellTaskOrganizerTests { assertTrue(mwListener.appeared.contains(task2)); } + @Test + public void testOnSizeCompatActivityChanged() { + final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN); + taskInfo1.displayId = DEFAULT_DISPLAY; + taskInfo1.topActivityToken = mock(IBinder.class); + taskInfo1.topActivityInSizeCompat = false; + final TrackingTaskListener taskListener = new TrackingTaskListener(); + mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN); + mOrganizer.onTaskAppeared(taskInfo1, null); + + // sizeCompatActivity is null if top activity is not in size compat. + verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + taskInfo1.configuration.windowConfiguration.getBounds(), + null /* sizeCompatActivity*/ , taskListener); + + // sizeCompatActivity is non-null if top activity is in size compat. + final RunningTaskInfo taskInfo2 = + createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); + taskInfo2.displayId = taskInfo1.displayId; + taskInfo2.topActivityToken = taskInfo1.topActivityToken; + taskInfo2.topActivityInSizeCompat = true; + mOrganizer.onTaskInfoChanged(taskInfo2); + verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + taskInfo1.configuration.windowConfiguration.getBounds(), + taskInfo1.topActivityToken, + taskListener); + + mOrganizer.onTaskVanished(taskInfo1); + verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId, + null /* taskConfig */, null /* sizeCompatActivity*/, + null /* taskListener */); + } + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index c2e4e149f8e00..b7aa907c6180d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -71,6 +71,8 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.sizecompatui.SizeCompatUI; +import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; @@ -293,8 +295,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, - Context context) { - return new ShellTaskOrganizer(mainExecutor, context); + Context context, SizeCompatUI sizeCompatUI) { + return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI); } @WMSingleton @@ -380,8 +382,7 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static FullscreenTaskListener provideFullscreenTaskListener( - SyncTransactionQueue syncQueue) { + static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) { return new FullscreenTaskListener(syncQueue); } @@ -392,4 +393,12 @@ public abstract class WMShellBaseModule { @ShellAnimationThread ShellExecutor animExecutor) { return new Transitions(organizer, pool, mainExecutor, animExecutor); } + + @WMSingleton + @Provides + static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController, + DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) { + return SizeCompatUIController.create(context, displayController, imeController, + mainExecutor); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 4078d4dad9840..79641dbaeef10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -18,7 +18,6 @@ package com.android.systemui.wmshell; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; @@ -41,7 +40,6 @@ import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedGestureHandler; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.phone.PipTouchHandler; import org.junit.Before; import org.junit.Test; @@ -51,6 +49,12 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; +/** + * Tests for {@link WMShell}. + * + * Build/Install/Run: + * atest SystemUITests:WMShellTest + */ @SmallTest @RunWith(AndroidJUnit4.class) public class WMShellTest extends SysuiTestCase {