From f834092444062d7ea6912092a6d3c2f8384f745c Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 8 Mar 2021 17:40:56 -0800 Subject: [PATCH] Allow non-resizable to enter split screen from recent Before, LegacySplitScreen is not allowing non-resizable to enter split screen. Now, it also checks supportsNonResizableMultiWindow. Fix: 181377395 Bug: 176061101 Test: manual Change-Id: Iae4c65b5c8b0a139dbf8af55caba9f6c99d1f470 --- .../java/android/app/ActivityTaskManager.java | 14 +++ .../android/app/IActivityTaskManager.aidl | 7 ++ .../android/wm/shell/apppairs/AppPair.java | 4 +- .../SplitScreenTransitions.java | 7 +- .../legacysplitscreen/WindowManagerProxy.java | 28 ++++- ...AppPairsTestCannotPairNonResizeableApps.kt | 31 ++++- ...ppPairsTestSupportPairNonResizeableApps.kt | 117 ++++++++++++++++++ .../flicker/apppairs/AppPairsTransition.kt | 2 + .../server/wm/ActivityTaskManagerService.java | 7 +- 9 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 233f737b8e0f7..a24f8716b1cc6 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -393,6 +393,20 @@ public class ActivityTaskManager { } } + /** + * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed + * if the request orientation is not met, and will be shown in size-compat mode if the container + * size has changed. + * @hide + */ + public static boolean supportsNonResizableMultiWindow() { + try { + return ActivityTaskManager.getService().supportsNonResizableMultiWindow(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc). * @hide diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 542f754ce3646..3bfddf7db015c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -289,6 +289,13 @@ interface IActivityTaskManager { void setSplitScreenResizing(boolean resizing); boolean supportsLocalVoiceInteraction(); + /** + * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed + * if the request orientation is not met, and will be shown in size-compat mode if the container + * size has changed. + */ + boolean supportsNonResizableMultiWindow(); + // Get device configuration ConfigurationInfo getDeviceConfigurationInfo(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index 562b32b41dd2b..b6d408afd7039 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerToken; @@ -88,7 +89,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s", task1.taskId, task2.taskId, this); - if (!task1.isResizeable || !task2.isResizeable) { + if ((!task1.isResizeable || !task2.isResizeable) + && !ActivityTaskManager.supportsNonResizableMultiWindow()) { ProtoLog.e(WM_SHELL_TASK_ORG, "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b", task1.isResizeable, task2.isResizeable); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java index eea5c08818cc9..d06064a82ff0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java @@ -30,6 +30,7 @@ import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.WindowConfiguration; import android.graphics.Rect; import android.os.IBinder; @@ -91,9 +92,11 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { // is nothing behind it. ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) && triggerTask.parentTaskId == mListener.mPrimary.taskId) - // if a non-resizable is launched, we also need to leave split-screen. + // if a non-resizable is launched when it is not supported in multi window, + // we also need to leave split-screen. || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) - && !triggerTask.isResizeable); + && !triggerTask.isResizeable + && !ActivityTaskManager.supportsNonResizableMultiWindow()); // In both cases, dismiss the primary if (shouldDismiss) { WindowManagerProxy.buildDismissSplit(out, mListener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index 82468ad999b4c..5a2ef568d82a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -46,6 +46,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; +import java.util.function.BooleanSupplier; /** * Proxy to simplify calls into window manager/activity manager @@ -208,11 +209,17 @@ class WindowManagerProxy { return false; } ActivityManager.RunningTaskInfo topHomeTask = null; + // One-time lazy wrapper to avoid duplicated IPC in loop. Not store as class variable + // because the value can be changed at runtime. + final BooleanSupplier supportsNonResizableMultiWindow = + createSupportsNonResizableMultiWindowSupplier(); for (int i = rootTasks.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); - // Only move resizeable task to split secondary. However, we have an exception - // for non-resizable home because we will minimize to show it. - if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) { + // Check whether to move resizeable task to split secondary. + // Also, we have an exception for non-resizable home because we will minimize to show + // it. + if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME + && !supportsNonResizableMultiWindow.getAsBoolean()) { continue; } // Only move fullscreen tasks to split secondary. @@ -357,6 +364,21 @@ class WindowManagerProxy { outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); } + /** Creates a lazy wrapper to get whether it supports non-resizable in multi window. */ + private static BooleanSupplier createSupportsNonResizableMultiWindowSupplier() { + return new BooleanSupplier() { + private Boolean mSupportsNonResizableMultiWindow; + @Override + public boolean getAsBoolean() { + if (mSupportsNonResizableMultiWindow == null) { + mSupportsNonResizableMultiWindow = + ActivityTaskManager.supportsNonResizableMultiWindow(); + } + return mSupportsNonResizableMultiWindow; + } + }; + } + /** * Utility to apply a sync transaction serially with other sync transactions. * diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 63968f333ff40..98ce2747d532d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock import android.platform.test.annotations.Presubmit +import android.provider.Settings import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -26,6 +27,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.wm.shell.flicker.appPairsDividerIsInvisible import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -33,11 +36,10 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test AppPairs launch. - * To run this test: `atest WMShellFlickerTests:AppPairsTest` - */ -/** - * Test cold launch app from launcher. + * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair + * non-resizable apps. + * * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps` */ @RequiresDevice @@ -47,6 +49,7 @@ import org.junit.runners.Parameterized class AppPairsTestCannotPairNonResizeableApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 override val transition: FlickerBuilder.(Map) -> Unit get() = { @@ -60,6 +63,24 @@ class AppPairsTestCannotPairNonResizeableApps( } } + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 1) { + // Not support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt new file mode 100644 index 0000000000000..1e3595c17f48a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -0,0 +1,117 @@ +/* + * 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.flicker.apppairs + +import android.os.SystemClock +import android.platform.test.annotations.Presubmit +import android.provider.Settings +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appPairsDividerIsVisible +import com.android.wm.shell.flicker.helpers.AppPairsHelper +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launch app from launcher. When the device supports non-resizable in multi window + * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair + * non-resizable apps. + * + * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AppPairsTestSupportPairNonResizeableApps( + testSpec: FlickerTestParameter +) : AppPairsTransition(testSpec) { + var prevSupportNonResizableInMultiWindow = 0 + + override val transition: FlickerBuilder.(Map) -> Unit + get() = { + super.transition(this, it) + transitions { + nonResizeableApp?.launchViaIntent(wmHelper) + // TODO pair apps through normal UX flow + executeShellCommand( + composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) + SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) + } + } + + @Before + fun setup() { + prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW) + if (prevSupportNonResizableInMultiWindow == 0) { + // Support non-resizable in multi window + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) + } + } + + @After + fun teardown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + prevSupportNonResizableInMultiWindow) + } + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + @FlakyTest + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + @Presubmit + @Test + fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() + + @Presubmit + @Test + fun bothAppWindowVisible() { + val nonResizeableApp = nonResizeableApp + require(nonResizeableApp != null) { + "Non resizeable app not initialized" + } + testSpec.assertWmEnd { + isVisible(nonResizeableApp.defaultWindowName) + isVisible(primaryApp.defaultWindowName) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = AppPairsHelper.TEST_REPETITIONS) + } + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 128560a0dd215..134d00be73e80 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation +import android.content.Context import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper import android.util.Log @@ -46,6 +47,7 @@ import java.io.IOException abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context protected val isRotated = testSpec.config.startRotation.isRotated() protected val activityHelper = ActivityHelper.getInstance() protected val appPairsHelper = AppPairsHelper(instrumentation, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d4eedf153c241..52d110c95e36a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -557,7 +557,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean mSupportsPictureInPicture; boolean mSupportsMultiDisplay; boolean mForceResizableActivities; - boolean mSupportsNonResizableMultiWindow; + volatile boolean mSupportsNonResizableMultiWindow; final List mScreenObservers = new ArrayList<>(); @@ -3431,6 +3431,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .supportsLocalVoiceInteraction(); } + @Override + public boolean supportsNonResizableMultiWindow() { + return mSupportsNonResizableMultiWindow; + } + @Override public boolean updateConfiguration(Configuration values) { mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");