summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/Android.bp26
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java128
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java93
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java (renamed from libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java)36
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java74
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java96
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java125
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java78
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java70
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java52
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java43
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java56
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java196
-rw-r--r--libs/WindowManager/Shell/Android.bp14
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java)11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java298
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java189
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java194
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java214
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java226
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java186
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java268
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java158
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java)10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java85
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt117
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt)76
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt123
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt145
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt)92
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt146
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt)85
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt16
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt38
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java137
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java41
-rw-r--r--libs/androidfw/AssetManager2.cpp3
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h5
-rw-r--r--libs/hwui/Properties.cpp2
-rw-r--r--libs/hwui/Properties.h8
-rw-r--r--libs/hwui/Readback.cpp16
-rw-r--r--libs/hwui/effects/StretchEffect.cpp56
-rw-r--r--libs/hwui/effects/StretchEffect.h17
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp11
-rw-r--r--libs/hwui/hwui/ImageDecoder.h4
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp3
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp11
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp7
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp20
-rw-r--r--libs/hwui/renderthread/VulkanManager.h16
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp2
144 files changed, 4737 insertions, 2289 deletions
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index d8f00bbc1ad3..dc4b5636a246 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -30,13 +30,20 @@ android_library_import {
java_library {
name: "androidx.window.sidecar",
- srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"],
+ srcs: [
+ "src/androidx/window/sidecar/**/*.java",
+ "src/androidx/window/util/**/*.java",
+ "src/androidx/window/common/**/*.java",
+ ],
static_libs: ["window-sidecar"],
installable: true,
sdk_version: "core_platform",
system_ext_specific: true,
- libs: ["framework", "androidx.annotation_annotation",],
- required: ["androidx.window.sidecar.xml",],
+ libs: [
+ "framework",
+ "androidx.annotation_annotation",
+ ],
+ required: ["androidx.window.sidecar.xml"],
}
prebuilt_etc {
@@ -58,13 +65,20 @@ android_library_import {
java_library {
name: "androidx.window.extensions",
- srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"],
+ srcs: [
+ "src/androidx/window/extensions/**/*.java",
+ "src/androidx/window/util/**/*.java",
+ "src/androidx/window/common/**/*.java",
+ ],
static_libs: ["window-extensions"],
installable: true,
sdk_version: "core_platform",
system_ext_specific: true,
- libs: ["framework", "androidx.annotation_annotation",],
- required: ["androidx.window.extensions.xml",],
+ libs: [
+ "framework",
+ "androidx.annotation_annotation",
+ ],
+ required: ["androidx.window.extensions.xml"],
}
prebuilt_etc {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java
new file mode 100644
index 000000000000..e6ad011e617e
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java
@@ -0,0 +1,128 @@
+/*
+ * 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 androidx.window.common;
+
+import static androidx.window.util.ExtensionHelper.isZero;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
+final class CommonDisplayFeature implements DisplayFeature {
+ private static final Pattern FEATURE_PATTERN =
+ Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
+
+ private static final String FEATURE_TYPE_FOLD = "fold";
+ private static final String FEATURE_TYPE_HINGE = "hinge";
+
+ // TODO(b/183049815): Support feature strings that include the state of the feature.
+ /**
+ * Parses a display feature from a string.
+ *
+ * @throws IllegalArgumentException if the provided string is improperly formatted or could not
+ * otherwise be parsed.
+ *
+ * @see #FEATURE_PATTERN
+ */
+ @NonNull
+ static CommonDisplayFeature parseFromString(@NonNull String string) {
+ Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
+ if (!featureMatcher.matches()) {
+ throw new IllegalArgumentException("Malformed feature description format: " + string);
+ }
+ try {
+ String featureType = featureMatcher.group(1);
+ int type;
+ switch (featureType) {
+ case FEATURE_TYPE_FOLD:
+ type = 1 /* TYPE_FOLD */;
+ break;
+ case FEATURE_TYPE_HINGE:
+ type = 2 /* TYPE_HINGE */;
+ break;
+ default: {
+ throw new IllegalArgumentException("Malformed feature type: " + featureType);
+ }
+ }
+
+ int left = Integer.parseInt(featureMatcher.group(2));
+ int top = Integer.parseInt(featureMatcher.group(3));
+ int right = Integer.parseInt(featureMatcher.group(4));
+ int bottom = Integer.parseInt(featureMatcher.group(5));
+ Rect featureRect = new Rect(left, top, right, bottom);
+ if (isZero(featureRect)) {
+ throw new IllegalArgumentException("Feature has empty bounds: " + string);
+ }
+
+ return new CommonDisplayFeature(type, null, featureRect);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Malformed feature description: " + string, e);
+ }
+ }
+
+ private final int mType;
+ @Nullable
+ private final Integer mState;
+ @NonNull
+ private final Rect mRect;
+
+ CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) {
+ this.mType = type;
+ this.mState = state;
+ if (rect.width() == 0 && rect.height() == 0) {
+ throw new IllegalArgumentException(
+ "Display feature rectangle cannot have zero width and height simultaneously.");
+ }
+ this.mRect = rect;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ /** Returns the state of the feature, or {@code null} if the feature has no state. */
+ @Nullable
+ public Integer getState() {
+ return mState;
+ }
+
+ @NonNull
+ public Rect getRect() {
+ return mRect;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CommonDisplayFeature that = (CommonDisplayFeature) o;
+ return mType == that.mType
+ && Objects.equals(mState, that.mState)
+ && mRect.equals(that.mRect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mState, mRect);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java
new file mode 100644
index 000000000000..fa9a5a8b7a1b
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java
@@ -0,0 +1,93 @@
+/*
+ * 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 androidx.window.common;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import androidx.window.util.BaseDataProducer;
+
+import com.android.internal.R;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
+ * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
+ * config at {@link R.array#config_device_state_postures}.
+ */
+public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> {
+ private static final String TAG = "ConfigDevicePostureProducer";
+ private static final boolean DEBUG = false;
+
+ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+ private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+
+ private final DeviceStateCallback mDeviceStateCallback = (state) -> {
+ mCurrentDeviceState = state;
+ notifyDataChanged();
+ };
+
+ public DeviceStateManagerPostureProducer(@NonNull Context context) {
+ String[] deviceStatePosturePairs = context.getResources()
+ .getStringArray(R.array.config_device_state_postures);
+ for (String deviceStatePosturePair : deviceStatePosturePairs) {
+ String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+ if (deviceStatePostureMapping.length != 2) {
+ if (DEBUG) {
+ Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair);
+ }
+ continue;
+ }
+
+ int deviceState;
+ int posture;
+ try {
+ deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+ posture = Integer.parseInt(deviceStatePostureMapping[1]);
+ } catch (NumberFormatException e) {
+ if (DEBUG) {
+ Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair,
+ e);
+ }
+ continue;
+ }
+
+ mDeviceStateToPostureMap.put(deviceState, posture);
+ }
+
+ if (mDeviceStateToPostureMap.size() > 0) {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
+ }
+ }
+
+ @Override
+ @Nullable
+ public Optional<Integer> getData() {
+ final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1);
+ return posture != -1 ? Optional.of(posture) : Optional.empty();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java
index b74a2a4bd47d..b6c4c436d0b1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java
@@ -14,39 +14,23 @@
* limitations under the License.
*/
-package androidx.window.util;
+package androidx.window.common;
+import android.annotation.Nullable;
import android.graphics.Rect;
import androidx.annotation.NonNull;
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
-public class BaseDisplayFeature {
- private final int mType;
- private final int mState;
- @NonNull
- public final Rect mRect;
-
- public BaseDisplayFeature(int type, int state, @NonNull Rect rect) {
- this.mType = type;
- this.mState = state;
- if (rect.width() == 0 && rect.height() == 0) {
- throw new IllegalArgumentException(
- "Display feature rectangle cannot have zero width and height simultaneously.");
- }
- this.mRect = rect;
- }
-
- public int getType() {
- return mType;
- }
+public interface DisplayFeature {
+ /** Returns the type of the feature. */
+ int getType();
- public int getState() {
- return mState;
- }
+ /** Returns the state of the feature, or {@code null} if the feature has no state. */
+ @Nullable
+ Integer getState();
+ /** Returns the bounds of the feature. */
@NonNull
- public Rect getRect() {
- return mRect;
- }
+ Rect getRect();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java
new file mode 100644
index 000000000000..cd2cadc082e1
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java
@@ -0,0 +1,74 @@
+/*
+ * 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 androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.window.util.BaseDataProducer;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces
+ * {@link CommonDisplayFeature} parsed from a string stored in the resources config at
+ * {@link R.string#config_display_features}.
+ */
+public final class ResourceConfigDisplayFeatureProducer extends
+ BaseDataProducer<List<DisplayFeature>> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ResourceConfigDisplayFeatureProducer";
+
+ private final Context mContext;
+
+ public ResourceConfigDisplayFeatureProducer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @Override
+ @Nullable
+ public Optional<List<DisplayFeature>> getData() {
+ String displayFeaturesString = mContext.getResources().getString(
+ R.string.config_display_features);
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ return Optional.empty();
+ }
+
+ List<DisplayFeature> features = new ArrayList<>();
+ String[] featureStrings = displayFeaturesString.split(";");
+ for (String featureString : featureStrings) {
+ CommonDisplayFeature feature;
+ try {
+ feature = CommonDisplayFeature.parseFromString(featureString);
+ } catch (IllegalArgumentException e) {
+ if (DEBUG) {
+ Log.w(TAG, "Failed to parse display feature: " + featureString, e);
+ }
+ continue;
+ }
+ features.add(feature);
+ }
+ return Optional.of(features);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java
new file mode 100644
index 000000000000..2026df3fa979
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java
@@ -0,0 +1,96 @@
+/*
+ * 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 androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.window.util.BaseDataProducer;
+
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture
+ * as an {@link Integer} from a value stored in {@link Settings}.
+ */
+public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> {
+ private static final String DEVICE_POSTURE = "device_posture";
+
+ private final Uri mDevicePostureUri =
+ Settings.Global.getUriFor(DEVICE_POSTURE);
+
+ private final ContentResolver mResolver;
+ private final ContentObserver mObserver;
+ private boolean mRegisteredObservers;
+
+ public SettingsDevicePostureProducer(@NonNull Context context) {
+ mResolver = context.getContentResolver();
+ mObserver = new SettingsObserver();
+ }
+
+ @Override
+ @Nullable
+ public Optional<Integer> getData() {
+ int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1);
+ return posture == -1 ? Optional.empty() : Optional.of(posture);
+ }
+
+ /**
+ * Registers settings observers, if needed. When settings observers are registered for this
+ * producer callbacks for changes in data will be triggered.
+ */
+ public void registerObserversIfNeeded() {
+ if (mRegisteredObservers) {
+ return;
+ }
+ mRegisteredObservers = true;
+ mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
+ mObserver /* ContentObserver */);
+ }
+
+ /**
+ * Unregisters settings observers, if needed. When settings observers are unregistered for this
+ * producer callbacks for changes in data will not be triggered.
+ */
+ public void unregisterObserversIfNeeded() {
+ if (!mRegisteredObservers) {
+ return;
+ }
+ mRegisteredObservers = false;
+ mResolver.unregisterContentObserver(mObserver);
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mDevicePostureUri.equals(uri)) {
+ notifyDataChanged();
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
new file mode 100644
index 000000000000..040662657a74
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
@@ -0,0 +1,125 @@
+/*
+ * 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 androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.window.util.BaseDataProducer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces
+ * {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}.
+ */
+public final class SettingsDisplayFeatureProducer
+ extends BaseDataProducer<List<DisplayFeature>> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SettingsDisplayFeatureProducer";
+ private static final String DISPLAY_FEATURES = "display_features";
+
+ private final Uri mDisplayFeaturesUri =
+ Settings.Global.getUriFor(DISPLAY_FEATURES);
+
+ private final ContentResolver mResolver;
+ private final ContentObserver mObserver;
+ private boolean mRegisteredObservers;
+
+ public SettingsDisplayFeatureProducer(@NonNull Context context) {
+ mResolver = context.getContentResolver();
+ mObserver = new SettingsObserver();
+ }
+
+ @Override
+ @Nullable
+ public Optional<List<DisplayFeature>> getData() {
+ String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
+ if (displayFeaturesString == null) {
+ return Optional.empty();
+ }
+
+ List<DisplayFeature> features = new ArrayList<>();
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ return Optional.of(features);
+ }
+ String[] featureStrings = displayFeaturesString.split(";");
+
+ for (String featureString : featureStrings) {
+ CommonDisplayFeature feature;
+ try {
+ feature = CommonDisplayFeature.parseFromString(featureString);
+ } catch (IllegalArgumentException e) {
+ if (DEBUG) {
+ Log.w(TAG, "Failed to parse display feature: " + featureString, e);
+ }
+ continue;
+ }
+ features.add(feature);
+ }
+ return Optional.of(features);
+ }
+
+ /**
+ * Registers settings observers, if needed. When settings observers are registered for this
+ * producer callbacks for changes in data will be triggered.
+ */
+ public void registerObserversIfNeeded() {
+ if (mRegisteredObservers) {
+ return;
+ }
+ mRegisteredObservers = true;
+ mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
+ mObserver /* ContentObserver */);
+ }
+
+ /**
+ * Unregisters settings observers, if needed. When settings observers are unregistered for this
+ * producer callbacks for changes in data will not be triggered.
+ */
+ public void unregisterObserversIfNeeded() {
+ if (!mRegisteredObservers) {
+ return;
+ }
+ mRegisteredObservers = false;
+ mResolver.unregisterContentObserver(mObserver);
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mDisplayFeaturesUri.equals(uri)) {
+ notifyDataChanged();
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
index 5c91cf41bfc6..ce9be6a7bba3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -27,11 +27,17 @@ import android.graphics.Rect;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.window.util.BaseDisplayFeature;
-import androidx.window.util.SettingsConfigProvider;
+import androidx.window.common.DeviceStateManagerPostureProducer;
+import androidx.window.common.DisplayFeature;
+import androidx.window.common.ResourceConfigDisplayFeatureProducer;
+import androidx.window.common.SettingsDevicePostureProducer;
+import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.util.DataProducer;
+import androidx.window.util.PriorityDataProducer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/**
* Reference implementation of androidx.window.extensions OEM interface for use with
@@ -41,23 +47,46 @@ import java.util.List;
* production builds since the interface can still change before reaching stable version.
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
-class SampleExtensionImpl extends StubExtension implements
- SettingsConfigProvider.StateChangeCallback {
+class SampleExtensionImpl extends StubExtension {
private static final String TAG = "SampleExtension";
- private final SettingsConfigProvider mConfigProvider;
+ private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
+ private final DataProducer<Integer> mDevicePostureProducer;
+
+ private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
+ private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
SampleExtensionImpl(Context context) {
- mConfigProvider = new SettingsConfigProvider(context, this);
+ mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
+ mDevicePostureProducer = new PriorityDataProducer<>(List.of(
+ mSettingsDevicePostureProducer,
+ new DeviceStateManagerPostureProducer(context)
+ ));
+
+ mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
+ mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
+ mSettingsDisplayFeatureProducer,
+ new ResourceConfigDisplayFeatureProducer(context)
+ ));
+
+ mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
+ mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- @Override
- public void onDevicePostureChanged() {
- updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState()));
+ private void onDevicePostureChanged() {
+ updateDeviceState(new ExtensionDeviceState(getDevicePosture()));
+
+ // Trigger a change in display features as the posture will be used in place of the feature
+ // state if the state is left unset by the producer.
+ onDisplayFeaturesChanged();
}
- @Override
- public void onDisplayFeaturesChanged() {
+ private int getDevicePosture() {
+ Optional<Integer> posture = mDevicePostureProducer.getData();
+ return posture.orElse(ExtensionDeviceState.POSTURE_UNKNOWN);
+ }
+
+ private void onDisplayFeaturesChanged() {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
updateWindowLayout(activity, newLayout);
@@ -84,13 +113,20 @@ class SampleExtensionImpl extends StubExtension implements
return features;
}
- List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
- for (BaseDisplayFeature baseFeature : storedFeatures) {
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
- baseFeature.getState()));
+ Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
+ if (storedFeatures.isPresent()) {
+ int posture = getDevicePosture();
+
+ for (DisplayFeature baseFeature : storedFeatures.get()) {
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+
+ Integer featureState = baseFeature.getState();
+
+ features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
+ featureState == null ? posture : featureState));
+ }
}
return features;
}
@@ -98,9 +134,11 @@ class SampleExtensionImpl extends StubExtension implements
@Override
protected void onListenersChanged() {
if (hasListeners()) {
- mConfigProvider.registerObserversIfNeeded();
+ mSettingsDevicePostureProducer.registerObserversIfNeeded();
+ mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
} else {
- mConfigProvider.unregisterObserversIfNeeded();
+ mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
+ mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
}
onDevicePostureChanged();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index d3700f88d97f..ece198cad818 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -29,33 +29,53 @@ import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.window.util.BaseDisplayFeature;
-import androidx.window.util.SettingsConfigProvider;
+import androidx.window.common.DeviceStateManagerPostureProducer;
+import androidx.window.common.DisplayFeature;
+import androidx.window.common.ResourceConfigDisplayFeatureProducer;
+import androidx.window.common.SettingsDevicePostureProducer;
+import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.util.DataProducer;
+import androidx.window.util.PriorityDataProducer;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/**
* Reference implementation of androidx.window.sidecar OEM interface for use with
* WindowManager Jetpack.
*/
-class SampleSidecarImpl extends StubSidecar implements
- SettingsConfigProvider.StateChangeCallback {
+class SampleSidecarImpl extends StubSidecar {
private static final String TAG = "SampleSidecar";
- private final SettingsConfigProvider mConfigProvider;
+ private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
+ private final DataProducer<Integer> mDevicePostureProducer;
+
+ private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
+ private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
SampleSidecarImpl(Context context) {
- mConfigProvider = new SettingsConfigProvider(context, this);
+ mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
+ mDevicePostureProducer = new PriorityDataProducer<>(List.of(
+ mSettingsDevicePostureProducer,
+ new DeviceStateManagerPostureProducer(context)
+ ));
+
+ mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
+ mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
+ mSettingsDisplayFeatureProducer,
+ new ResourceConfigDisplayFeatureProducer(context)
+ ));
+
+ mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
+ mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- @Override
- public void onDevicePostureChanged() {
+ private void onDevicePostureChanged() {
updateDeviceState(getDeviceState());
}
- @Override
- public void onDisplayFeaturesChanged() {
+ private void onDisplayFeaturesChanged() {
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
updateWindowLayout(windowToken, newLayout);
@@ -65,8 +85,10 @@ class SampleSidecarImpl extends StubSidecar implements
@NonNull
@Override
public SidecarDeviceState getDeviceState() {
+ Optional<Integer> posture = mDevicePostureProducer.getData();
+
SidecarDeviceState deviceState = new SidecarDeviceState();
- deviceState.posture = mConfigProvider.getDeviceState();
+ deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN);
return deviceState;
}
@@ -96,15 +118,17 @@ class SampleSidecarImpl extends StubSidecar implements
return features;
}
- List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
- for (BaseDisplayFeature baseFeature : storedFeatures) {
- SidecarDisplayFeature feature = new SidecarDisplayFeature();
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- feature.setRect(featureRect);
- feature.setType(baseFeature.getType());
- features.add(feature);
+ Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
+ if (storedFeatures.isPresent()) {
+ for (DisplayFeature baseFeature : storedFeatures.get()) {
+ SidecarDisplayFeature feature = new SidecarDisplayFeature();
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+ feature.setRect(featureRect);
+ feature.setType(baseFeature.getType());
+ features.add(feature);
+ }
}
return features;
}
@@ -112,9 +136,11 @@ class SampleSidecarImpl extends StubSidecar implements
@Override
protected void onListenersChanged() {
if (hasListeners()) {
- mConfigProvider.registerObserversIfNeeded();
+ mSettingsDevicePostureProducer.registerObserversIfNeeded();
+ mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
} else {
- mConfigProvider.unregisterObserversIfNeeded();
+ mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
+ mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
new file mode 100644
index 000000000000..0a46703451ab
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -0,0 +1,52 @@
+/*
+ * 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 androidx.window.util;
+
+import androidx.annotation.NonNull;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Base class that provides the implementation for the callback mechanism of the
+ * {@link DataProducer} API.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public abstract class BaseDataProducer<T> implements DataProducer<T> {
+ private final Set<Runnable> mCallbacks = new LinkedHashSet<>();
+
+ @Override
+ public final void addDataChangedCallback(@NonNull Runnable callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public final void removeDataChangedCallback(@NonNull Runnable callback) {
+ mCallbacks.remove(callback);
+ }
+
+ /**
+ * Called to notify all registered callbacks that the data provided by {@link #getData()} has
+ * changed.
+ */
+ protected void notifyDataChanged() {
+ for (Runnable callback : mCallbacks) {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
new file mode 100644
index 000000000000..d4d1a23b756b
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
@@ -0,0 +1,43 @@
+/*
+ * 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 androidx.window.util;
+
+import android.annotation.NonNull;
+
+import java.util.Optional;
+
+/**
+ * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when
+ * the data managed by the produces has changed.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public interface DataProducer<T> {
+ /**
+ * Returns the data currently stored in the provider, or {@link Optional#empty()} if the
+ * provider has no data.
+ */
+ Optional<T> getData();
+
+ /**
+ * Adds a callback to be notified when the data returned from {@link #getData()} has changed.
+ */
+ void addDataChangedCallback(@NonNull Runnable callback);
+
+ /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */
+ void removeDataChangedCallback(@NonNull Runnable callback);
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
new file mode 100644
index 000000000000..990ae20cc934
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
@@ -0,0 +1,56 @@
+/*
+ * 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 androidx.window.util;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of
+ * provided child producers.
+ * <p>
+ * The value returned is based on the precedence of the supplied children where the producer with
+ * index 0 has a higher precedence than producers that come later in the list. When a producer with
+ * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be
+ * returned from an instance of this class, ignoring all other producers with lower precedence.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public final class PriorityDataProducer<T> extends BaseDataProducer<T> {
+ private final List<DataProducer<T>> mChildProducers;
+
+ public PriorityDataProducer(List<DataProducer<T>> childProducers) {
+ mChildProducers = childProducers;
+ for (DataProducer<T> childProducer : mChildProducers) {
+ childProducer.addDataChangedCallback(this::notifyDataChanged);
+ }
+ }
+
+ @Nullable
+ @Override
+ public Optional<T> getData() {
+ for (DataProducer<T> childProducer : mChildProducers) {
+ final Optional<T> data = childProducer.getData();
+ if (data.isPresent()) {
+ return data;
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
deleted file mode 100644
index 6dd190ce2519..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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 androidx.window.util;
-
-import static androidx.window.util.ExtensionHelper.isZero;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Device and display feature state provider that uses Settings as the source.
- */
-public final class SettingsConfigProvider extends ContentObserver {
- private static final String TAG = "SettingsConfigProvider";
- private static final String DEVICE_POSTURE = "device_posture";
- private static final String DISPLAY_FEATURES = "display_features";
-
- private static final Pattern FEATURE_PATTERN =
- Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
-
- private static final String FEATURE_TYPE_FOLD = "fold";
- private static final String FEATURE_TYPE_HINGE = "hinge";
-
- private final Uri mDevicePostureUri =
- Settings.Global.getUriFor(DEVICE_POSTURE);
- private final Uri mDisplayFeaturesUri =
- Settings.Global.getUriFor(DISPLAY_FEATURES);
- private final Context mContext;
- private final ContentResolver mResolver;
- private final StateChangeCallback mCallback;
- private boolean mRegisteredObservers;
-
- public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) {
- super(new Handler(Looper.getMainLooper()));
- mContext = context;
- mResolver = context.getContentResolver();
- mCallback = callback;
- }
-
- /**
- * Registers the content observers for Settings keys that store device state and display feature
- * configurations.
- */
- public void registerObserversIfNeeded() {
- if (mRegisteredObservers) {
- return;
- }
- mRegisteredObservers = true;
- mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
- this /* ContentObserver */);
- mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
- this /* ContentObserver */);
- }
-
- /**
- * Unregisters the content observers that are tracking the state changes.
- * @see #registerObserversIfNeeded()
- */
- public void unregisterObserversIfNeeded() {
- if (!mRegisteredObservers) {
- return;
- }
- mRegisteredObservers = false;
- mResolver.unregisterContentObserver(this);
- }
-
- /**
- * Gets the device posture int stored in Settings.
- */
- public int getDeviceState() {
- return Settings.Global.getInt(mResolver, DEVICE_POSTURE,
- 0 /* POSTURE_UNKNOWN */);
- }
-
- /**
- * Gets the list of all display feature configs stored in Settings. Uses a custom
- * {@link BaseDisplayFeature} class to report the config to be translated for actual
- * containers in Sidecar or Extensions.
- */
- public List<BaseDisplayFeature> getDisplayFeatures() {
- List<BaseDisplayFeature> features = new ArrayList<>();
- String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
- if (TextUtils.isEmpty(displayFeaturesString)) {
- displayFeaturesString = mContext.getResources().getString(
- R.string.config_display_features);
- }
- if (TextUtils.isEmpty(displayFeaturesString)) {
- return features;
- }
- String[] featureStrings = displayFeaturesString.split(";");
-
- int deviceState = getDeviceState();
-
- for (String featureString : featureStrings) {
- Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString);
- if (!featureMatcher.matches()) {
- Log.e(TAG, "Malformed feature description format: " + featureString);
- continue;
- }
- try {
- String featureType = featureMatcher.group(1);
- int type;
- switch (featureType) {
- case FEATURE_TYPE_FOLD:
- type = 1 /* TYPE_FOLD */;
- break;
- case FEATURE_TYPE_HINGE:
- type = 2 /* TYPE_HINGE */;
- break;
- default: {
- Log.e(TAG, "Malformed feature type: " + featureType);
- continue;
- }
- }
-
- int left = Integer.parseInt(featureMatcher.group(2));
- int top = Integer.parseInt(featureMatcher.group(3));
- int right = Integer.parseInt(featureMatcher.group(4));
- int bottom = Integer.parseInt(featureMatcher.group(5));
- Rect featureRect = new Rect(left, top, right, bottom);
- if (!isZero(featureRect)) {
- BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState,
- featureRect);
- features.add(feature);
- } else {
- Log.w(TAG, "Read empty feature");
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "Malformed feature description: " + featureString);
- }
- }
- return features;
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (uri == null) {
- return;
- }
-
- if (mDevicePostureUri.equals(uri)) {
- mCallback.onDevicePostureChanged();
- mCallback.onDisplayFeaturesChanged();
- return;
- }
- if (mDisplayFeaturesUri.equals(uri)) {
- mCallback.onDisplayFeaturesChanged();
- }
- }
-
- /**
- * Callback that notifies about device or display feature state changes.
- */
- public interface StateChangeCallback {
- /**
- * Notifies about the device state update.
- */
- void onDevicePostureChanged();
-
- /**
- * Notifies about the display feature config update.
- */
- void onDisplayFeaturesChanged();
- }
-}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1b5dc8bdbcaa..18f019dc949b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -38,6 +38,14 @@ filegroup {
path: "src",
}
+filegroup {
+ name: "wm_shell-aidls",
+ srcs: [
+ "src/**/*.aidl",
+ ],
+ path: "src",
+}
+
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
filegroup {
name: "wm_shell-sources-kt",
@@ -98,7 +106,7 @@ android_library {
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
- "src/**/I*.aidl",
+ ":wm_shell-aidls",
],
resource_dirs: [
"res",
@@ -110,14 +118,14 @@ android_library {
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
"androidx.dynamicanimation_dynamicanimation",
+ "androidx.recyclerview_recyclerview",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"iconloader_base",
"jsr330",
"protolog-lib",
- "SettingsLib",
"WindowManager-Shell-proto",
- "jsr330"
+ "jsr330",
],
kotlincflags: ["-Xjvm-default=enable"],
manifest: "AndroidManifest.xml",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index d2b3cf6a4fe2..6bd0e0a4ff86 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell">
+ <!-- System permission required by WM Shell Task Organizer. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index b75ee4507e94..6edaf6fd6c35 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -22,9 +22,9 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"הגדרות"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולכבות את התכונה."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולהשבית את התכונה."</string>
<string name="pip_play" msgid="3496151081459417097">"הפעלה"</string>
- <string name="pip_pause" msgid="690688849510295232">"השהה"</string>
+ <string name="pip_pause" msgid="690688849510295232">"השהיה"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"אפשר לדלג אל הבא"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"אפשר לדלג אל הקודם"</string>
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"שינוי גודל"</string>
@@ -41,13 +41,13 @@
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"מסך עליון מלא"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"עליון 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string>
- <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"עליון 30%"</string>
+ <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"למעלה 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"מסך תחתון מלא"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש במצב שימוש ביד אחת"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"יציאה ממצב שימוש ביד אחת"</string>
- <string name="bubbles_settings_button_description" msgid="1301286017420516912">"הגדרות בשביל בועות של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="bubbles_settings_button_description" msgid="1301286017420516912">"הגדרות לבועות של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"גלישה"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"הוספה בחזרה לערימה"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> מהאפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 1527e8983079..fcd706f7e732 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -58,14 +58,14 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels weergeven"</string>
+ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatten met bubbels"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden weergegeven als zwevende iconen of \'bubbels\'. Tik om een bubbel te openen. Sleep om de bubbel te verplaatsen."</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden als zwevende iconen of bubbels getoond. Tik om een bubbel te openen. Sleep om een bubbel te verplaatsen."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Beheer bubbels wanneer je wilt"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tik op Beheren om bubbels van deze app uit te schakelen"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Geen recente bubbels"</string>
- <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels worden hier weergegeven"</string>
+ <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bubbel gesloten."</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c2f591b9d7af..0e783779866e 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -15,7 +15,13 @@
limitations under the License.
-->
<resources>
- <!-- Animation duration for resizing of PIP when entering/exiting. -->
+ <!-- Animation duration for PIP when entering. -->
+ <integer name="config_pipEnterAnimationDuration">425</integer>
+
+ <!-- Animation duration for PIP when exiting. -->
+ <integer name="config_pipExitAnimationDuration">250</integer>
+
+ <!-- Animation duration for resizing of PIP. -->
<integer name="config_pipResizeAnimationDuration">425</integer>
<!-- Allow dragging the PIP to a location to close it -->
@@ -43,12 +49,6 @@
when the PIP menu is shown in center. -->
<string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
- <!-- one handed background panel default color RGB -->
- <item name="config_one_handed_background_rgb" format="float" type="dimen">0.5</item>
-
- <!-- one handed background panel default alpha -->
- <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
-
<!-- maximum animation duration for the icon when entering the starting window -->
<integer name="max_starting_window_intro_icon_anim_duration">1000</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index cb54021d7a23..34c66a4f4b82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -16,6 +16,8 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+
import android.annotation.UiContext;
import android.app.ResourcesManager;
import android.content.Context;
@@ -34,6 +36,8 @@ import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -44,14 +48,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName();
- // Display area info. mapped by displayIds.
+ /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
- // Display area leashes. mapped by displayIds.
+ /** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners =
new SparseArray<>();
-
+ /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
private final Context mContext;
@@ -119,7 +123,7 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
@Override
@@ -161,24 +165,28 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
/**
- * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by
- * {@code displayId}.
- *
- * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is
- * associated with
- * @param newConfig The propagated configuration
+ * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
+ * {@link DisplayAreaInfo#displayId}.
*/
- private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) {
+ private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ final Display display = mContext.getSystemService(DisplayManager.class)
+ .getDisplay(displayId);
+ if (display == null) {
+ ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed."
+ + " Skip following steps", displayId);
+ return;
+ }
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
- daContext = new DisplayAreaContext(mContext, displayId);
+ daContext = new DisplayAreaContext(mContext, display);
mDisplayAreaContexts.put(displayId, daContext);
}
- daContext.updateConfigurationChanges(newConfig);
+ daContext.updateConfigurationChanges(displayAreaInfo.configuration);
}
/**
@@ -228,10 +236,8 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
private final IBinder mToken = new Binder();
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
- public DisplayAreaContext(@NonNull Context context, int displayId) {
+ public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
super(null);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(displayId);
attachBaseContext(context.createTokenContext(mToken, display));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index eaed24d6195a..d451f4a0661b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -47,21 +47,7 @@ public final class ShellCommandHandlerImpl {
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
- public static ShellCommandHandler create(
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
- ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
- splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
- appPairsOptional, mainExecutor).mImpl;
- }
-
- private ShellCommandHandlerImpl(
+ public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
@@ -80,6 +66,10 @@ public final class ShellCommandHandlerImpl {
mMainExecutor = mainExecutor;
}
+ public ShellCommandHandler asShellCommandHandler() {
+ return mImpl;
+ }
+
/** Dumps WM Shell internal state. */
private void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 85bd24c1c2bf..d1fbf31e2b99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -19,6 +19,7 @@ package com.android.wm.shell;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -26,7 +27,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -40,6 +41,7 @@ public class ShellInitImpl {
private final DisplayImeController mDisplayImeController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final Optional<BubbleController> mBubblesOptional;
private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<AppPairsController> mAppPairsOptional;
@@ -47,48 +49,26 @@ public class ShellInitImpl {
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
- private final Optional<StartingSurface> mStartingSurfaceOptional;
+ private final StartingWindowController mStartingWindow;
private final InitImpl mImpl = new InitImpl();
- public static ShellInit create(DisplayImeController displayImeController,
+ public ShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ Optional<BubbleController> bubblesOptional,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
- Optional<StartingSurface> startingSurfaceOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Transitions transitions,
- ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayImeController,
- dragAndDropController,
- shellTaskOrganizer,
- legacySplitScreenOptional,
- splitScreenOptional,
- appPairsOptional,
- startingSurfaceOptional,
- pipTouchHandlerOptional,
- fullscreenTaskListener,
- transitions,
- mainExecutor).mImpl;
- }
-
- private ShellInitImpl(DisplayImeController displayImeController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
- Optional<StartingSurface> startingSurfaceOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
+ StartingWindowController startingWindow,
ShellExecutor mainExecutor) {
mDisplayImeController = displayImeController;
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
+ mBubblesOptional = bubblesOptional;
mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mAppPairsOptional = appPairsOptional;
@@ -96,21 +76,26 @@ public class ShellInitImpl {
mPipTouchHandlerOptional = pipTouchHandlerOptional;
mTransitions = transitions;
mMainExecutor = mainExecutor;
- mStartingSurfaceOptional = startingSurfaceOptional;
+ mStartingWindow = startingWindow;
+ }
+
+ public ShellInit asShellInit() {
+ return mImpl;
}
private void init() {
// Start listening for display changes
mDisplayImeController.startMonitorDisplays();
+ // Setup the shell organizer
mShellTaskOrganizer.addListenerForType(
mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface);
- // Register the shell organizer
+ mShellTaskOrganizer.initStartingWindow(mStartingWindow);
mShellTaskOrganizer.registerOrganizer();
mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
+ mBubblesOptional.ifPresent(BubbleController::initialize);
// Bind the splitscreen impl to the drag drop controller
mDragAndDropController.initialize(mSplitScreenOptional);
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 fcb53cd890b7..94d13eab4299 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -48,7 +48,7 @@ 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.SizeCompatUIController;
-import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.startingsurface.StartingWindowController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -133,7 +133,7 @@ public class ShellTaskOrganizer extends TaskOrganizer {
private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
private final Object mLock = new Object();
- private StartingSurface mStartingSurface;
+ private StartingWindowController mStartingWindow;
/**
* In charge of showing size compat UI. Can be {@code null} if device doesn't support size
@@ -184,8 +184,8 @@ public class ShellTaskOrganizer extends TaskOrganizer {
/**
* @hide
*/
- public void initStartingSurface(StartingSurface startingSurface) {
- mStartingSurface = startingSurface;
+ public void initStartingWindow(StartingWindowController startingWindow) {
+ mStartingWindow = startingWindow;
}
/**
@@ -302,23 +302,23 @@ public class ShellTaskOrganizer extends TaskOrganizer {
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
- if (mStartingSurface != null) {
- mStartingSurface.addStartingWindow(info, appToken);
+ if (mStartingWindow != null) {
+ mStartingWindow.addStartingWindow(info, appToken);
}
}
@Override
public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
- if (mStartingSurface != null) {
- mStartingSurface.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ if (mStartingWindow != null) {
+ mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
}
}
@Override
public void copySplashScreenView(int taskId) {
- if (mStartingSurface != null) {
- mStartingSurface.copySplashScreenView(taskId);
+ if (mStartingWindow != null) {
+ mStartingWindow.copySplashScreenView(taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 46884fefd69c..7d65a08ce798 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -83,6 +83,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private boolean mIsInitialized;
private Listener mListener;
private Executor mListenerExecutor;
+ private Rect mObscuredTouchRect;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
@@ -161,6 +162,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
}
/**
+ * Indicates a region of the view that is not touchable.
+ *
+ * @param obscuredRect the obscured region of the view.
+ */
+ public void setObscuredTouchRect(Rect obscuredRect) {
+ mObscuredTouchRect = obscuredRect;
+ }
+
+ /**
* Call when view position or size has changed. Do not call when animating.
*/
public void onLocationChanged() {
@@ -384,6 +394,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+
+ if (mObscuredTouchRect != null) {
+ inoutInfo.touchableRegion.union(mObscuredTouchRect);
+ }
}
@Override
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 562b32b41dd2..b6d408afd703 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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 620c382ef183..4ff1ce97ba3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -120,6 +120,16 @@ public class BubbleController {
@Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
+ private final WindowManagerShellWrapper mWindowManagerShellWrapper;
+ private final LauncherApps mLauncherApps;
+ private final IStatusBarService mBarService;
+ private final WindowManager mWindowManager;
+ private final ShellTaskOrganizer mTaskOrganizer;
+
+ // Used to post to main UI thread
+ private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
+
private BubbleLogger mLogger;
private BubbleData mBubbleData;
private View mBubbleScrim;
@@ -148,12 +158,6 @@ public class BubbleController {
*/
@Nullable private BubbleEntry mNotifEntryToExpandOnShadeUnlock;
- private IStatusBarService mBarService;
- private WindowManager mWindowManager;
-
- // Used to post to main UI thread
- private final ShellExecutor mMainExecutor;
-
/** LayoutParams used to add the BubbleStackView to the window manager. */
private WindowManager.LayoutParams mWmLayoutParams;
/** Whether or not the BubbleStackView has been added to the WindowManager. */
@@ -177,15 +181,6 @@ public class BubbleController {
private boolean mInflateSynchronously;
- private ShellTaskOrganizer mTaskOrganizer;
-
- /**
- * Whether the IME is visible, as reported by the BubbleStackView. If it is, we'll make the
- * Bubbles window NOT_FOCUSABLE so that touches on the Bubbles UI doesn't steal focus from the
- * ActivityView and hide the IME.
- */
- private boolean mImeVisible = false;
-
/** true when user is in status bar unlock shade. */
private boolean mIsStatusBarShade = true;
@@ -231,13 +226,28 @@ public class BubbleController {
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mLauncherApps = launcherApps;
+ mBarService = statusBarService == null
+ ? IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE))
+ : statusBarService;
+ mWindowManager = windowManager;
+ mWindowManagerShellWrapper = windowManagerShellWrapper;
mFloatingContentCoordinator = floatingContentCoordinator;
mDataRepository = dataRepository;
mLogger = bubbleLogger;
mMainExecutor = mainExecutor;
-
+ mMainHandler = mainHandler;
+ mTaskOrganizer = organizer;
+ mSurfaceSynchronizer = synchronizer;
+ mCurrentUserId = ActivityManager.getCurrentUser();
mBubblePositioner = positioner;
mBubbleData = data;
+ mSavedBubbleKeysPerUser = new SparseSetArray<>();
+ mBubbleIconFactory = new BubbleIconFactory(context);
+ }
+
+ public void initialize() {
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(bubble -> {
// Make sure NoMan knows suppression state so that anyone querying it can tell.
@@ -261,28 +271,18 @@ public class BubbleController {
});
try {
- windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
+ mWindowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
} catch (RemoteException e) {
e.printStackTrace();
}
- mSurfaceSynchronizer = synchronizer;
- mWindowManager = windowManager;
- mBarService = statusBarService == null
- ? IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE))
- : statusBarService;
- mSavedBubbleKeysPerUser = new SparseSetArray<>();
- mCurrentUserId = ActivityManager.getCurrentUser();
mBubbleData.setCurrentUserId(mCurrentUserId);
- mBubbleIconFactory = new BubbleIconFactory(context);
- mTaskOrganizer = organizer;
mTaskOrganizer.addLocusIdListener((taskId, locus, visible) ->
mBubbleData.onLocusVisibilityChanged(taskId, locus, visible));
- launcherApps.registerCallback(new LauncherApps.Callback() {
+ mLauncherApps.registerCallback(new LauncherApps.Callback() {
@Override
public void onPackageAdded(String s, UserHandle userHandle) {}
@@ -318,7 +318,7 @@ public class BubbleController {
mBubbleData.removeBubblesWithInvalidShortcuts(
packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
}
- }, mainHandler);
+ }, mMainHandler);
}
@VisibleForTesting
@@ -527,10 +527,6 @@ public class BubbleController {
}
}
- void onImeVisibilityChanged(boolean imeVisible) {
- mImeVisible = imeVisible;
- }
-
/** Removes the BubbleStackView from the WindowManager if it's there. */
private void removeFromWindowManagerMaybe() {
if (!mAddedToWindowManager) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 2d9da215efb7..fe3f9ef6aa5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -93,7 +93,7 @@ public class BubbleIconFactory extends BaseIconFactory {
final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width);
final int importantConversationColor = mContext.getResources().getColor(
- com.android.settingslib.R.color.important_conversation, null);
+ R.color.important_conversation, null);
Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
Canvas c = new Canvas(badgeAndRing);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 64a44ca9e7e9..9eec48c02306 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -41,7 +41,6 @@ import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Region;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -152,6 +151,7 @@ public class BubbleStackView extends FrameLayout
* starting a new animation.
*/
private final ShellExecutor mDelayedAnimationExecutor;
+ private Runnable mDelayedAnimation;
/**
* Interface to synchronize {@link View} state and the screen.
@@ -876,8 +876,6 @@ public class BubbleStackView extends FrameLayout
mTaskbarScrim.setVisibility(GONE);
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- mBubbleController.onImeVisibilityChanged(
- insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
if (!mIsExpanded || mIsExpansionAnimating) {
return view.onApplyWindowInsets(insets);
}
@@ -1046,6 +1044,7 @@ public class BubbleStackView extends FrameLayout
}
};
+ // TODO: Create ManageMenuView and move setup / animations there
private void setUpManageMenu() {
if (mManageMenu != null) {
removeView(mManageMenu);
@@ -1865,7 +1864,7 @@ public class BubbleStackView extends FrameLayout
mExpandedBubble.getExpandedView().setAlphaAnimating(true);
}
- mDelayedAnimationExecutor.executeDelayed(() -> {
+ mDelayedAnimation = () -> {
mExpandedViewAlphaAnimator.start();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
@@ -1898,7 +1897,8 @@ public class BubbleStackView extends FrameLayout
}
})
.start();
- }, startDelay);
+ };
+ mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay);
}
private void animateCollapse() {
@@ -2097,7 +2097,7 @@ public class BubbleStackView extends FrameLayout
* animating flags for those animations.
*/
private void cancelDelayedExpandCollapseSwitchAnimations() {
- mDelayedAnimationExecutor.removeAllCallbacks();
+ mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation);
mIsExpansionAnimating = false;
mIsBubbleSwitchAnimating = false;
@@ -2146,50 +2146,6 @@ public class BubbleStackView extends FrameLayout
}
/**
- * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
- * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
- * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
- * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
- * the special nature of ActivityView, it does not respect the standard
- * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
- * this purpose.
- *
- * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
- * properties for performance reasons. This means that the default implementation of this method
- * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
- * it not receiving any touch events. This was previously addressed by returning false in the
- * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
- * touch handlers in the stack or its child views.
- *
- * To support touch handlers, we're overriding this method to leave the ActivityView's touchable
- * region alone. The only touchable part of the stack that can ever overlap the AV is a
- * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
- * updating the touchable region to allow users to grab a bubble while it completes its ~50ms
- * animation back to the bubble row.
- *
- * NOTE: Any future additions to the stack that obscure the ActivityView region will need their
- * bounds subtracted here in order to receive touch events.
- */
- @Override
- public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
- // If the notification shade is expanded, or the manage menu is open, or we are showing
- // manage bubbles user education, we shouldn't let the ActivityView steal any touch events
- // from any location.
- if (!mIsExpanded
- || mShowingManage
- || (mManageEduView != null
- && mManageEduView.getVisibility() == VISIBLE)) {
- touchableRegion.setEmpty();
- }
- }
-
- /**
- * If you're here because you're not receiving touch events on a view that is a descendant of
- * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
- * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
- * consumes all touch events within its bounds, even for views like the BubbleStackView that are
- * above it. It ignores typical view touch handling methods like this one and
- * dispatchTouchEvent.
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -2539,6 +2495,11 @@ public class BubbleStackView extends FrameLayout
}
mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+ if (mExpandedBubble.getExpandedView().getTaskView() != null) {
+ mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage
+ ? new Rect(0, 0, getWidth(), getHeight())
+ : null);
+ }
final boolean isLtr =
getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index f118b1e0b7a3..b7235a31af03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -150,6 +150,7 @@ public class DisplayLayout {
mDensityDpi = dl.mDensityDpi;
mHasNavigationBar = dl.mHasNavigationBar;
mHasStatusBar = dl.mHasStatusBar;
+ mNavBarFrameHeight = dl.mNavBarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
new file mode 100644
index 000000000000..b29058b1f204
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.common;
+
+import android.Manifest;
+import android.util.Slog;
+
+import java.util.function.Consumer;
+
+/**
+ * Helpers for working with executors
+ */
+public class ExecutorUtils {
+
+ /**
+ * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
+ * callback.
+ */
+ public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
+ String log, Consumer<T> callback) {
+ executeRemoteCallWithTaskPermission(controllerInstance, log, callback,
+ false /* blocking */);
+ }
+
+ /**
+ * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
+ * callback.
+ */
+ public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
+ String log, Consumer<T> callback, boolean blocking) {
+ if (controllerInstance == null) return;
+
+ final RemoteCallable<T> controller = controllerInstance;
+ controllerInstance.getContext().enforceCallingPermission(
+ Manifest.permission.MANAGE_ACTIVITY_TASKS, log);
+ if (blocking) {
+ try {
+ controllerInstance.getRemoteCallExecutor().executeBlocking(() -> {
+ callback.accept((T) controller);
+ });
+ } catch (InterruptedException e) {
+ Slog.e("ExecutorUtils", "Remote call failed", e);
+ }
+ } else {
+ controllerInstance.getRemoteCallExecutor().execute(() -> {
+ callback.accept((T) controller);
+ });
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index a4cd3c5a583d..bfee820870f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.common;
import android.annotation.NonNull;
import android.os.Handler;
-import android.os.Looper;
/** Executor implementation which is backed by a Handler. */
public class HandlerExecutor implements ShellExecutor {
@@ -47,11 +46,6 @@ public class HandlerExecutor implements ShellExecutor {
}
@Override
- public void removeAllCallbacks() {
- mHandler.removeCallbacksAndMessages(null);
- }
-
- @Override
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
new file mode 100644
index 000000000000..30f535ba940c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.common;
+
+import android.content.Context;
+
+/**
+ * An interface for controllers that can receive remote calls.
+ */
+public interface RemoteCallable<T> {
+ /**
+ * Returns a context used for permission checking.
+ */
+ Context getContext();
+
+ /**
+ * Returns the executor to post the handler callback to.
+ */
+ ShellExecutor getRemoteCallExecutor();
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 6abc8f6dda89..f729164ed303 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -16,16 +16,10 @@
package com.android.wm.shell.common;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.Trace;
-
import java.lang.reflect.Array;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-import java.util.function.BooleanSupplier;
-import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -94,11 +88,6 @@ public interface ShellExecutor extends Executor {
void executeDelayed(Runnable runnable, long delayMillis);
/**
- * Removes all pending callbacks.
- */
- void removeAllCallbacks();
-
- /**
* See {@link android.os.Handler#removeCallbacks}.
*/
void removeCallbacks(Runnable runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 4bb8e9b6581f..9dabec7a13d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -32,7 +32,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DragEvent;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.IWindowSession;
@@ -369,9 +369,9 @@ public class SystemWindows {
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
try {
- callbacks.onScrollCaptureResponse(
+ listener.onScrollCaptureResponse(
new ScrollCaptureResponse.Builder()
.setDescription("Not Implemented")
.build());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index bacff78e6a67..b64c796a1a43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -27,9 +27,10 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Rect;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import androidx.annotation.Nullable;
@@ -75,7 +76,7 @@ public class SplitLayout {
mDividerSize = mDividerWindowWidth - mDividerInsets * 2;
mRootBounds.set(configuration.windowConfiguration.getBounds());
- mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
}
@@ -114,7 +115,7 @@ public class SplitLayout {
mContext = mContext.createConfigurationContext(configuration);
mSplitWindowManager.setConfiguration(configuration);
mRootBounds.set(rootBounds);
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
// Don't inflate divider bar if it is not initialized.
@@ -217,15 +218,15 @@ public class SplitLayout {
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
- private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) {
+ private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
final boolean isLandscape = isLandscape(rootBounds);
return new DividerSnapAlgorithm(
- resources,
+ context.getResources(),
rootBounds.width(),
rootBounds.height(),
mDividerSize,
!isLandscape,
- new Rect() /* insets */,
+ getDisplayInsets(context),
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
@@ -250,6 +251,15 @@ public class SplitLayout {
animator.start();
}
+ private static Rect getDisplayInsets(Context context) {
+ return context.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics()
+ .getWindowInsets()
+ .getInsets(WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.displayCutout()).toRect();
+ }
+
private static boolean isLandscape(Rect bounds) {
return bounds.width() > bounds.height();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 17709438baba..58bf22ad29b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -85,7 +85,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
@Override
public void onDisplayAdded(int displayId) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId);
- final Context context = mDisplayController.getDisplayContext(displayId);
+ final Context context = mDisplayController.getDisplayContext(displayId)
+ .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
final WindowManager wm = context.getSystemService(WindowManager.class);
// TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index aab2334f8255..9a09ca43d1d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -235,7 +235,7 @@ public class DragAndDropPolicy {
mStarter.startShortcut(packageName, id, stage, position, opts, user);
} else {
mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
- mContext, null, stage, position, opts);
+ null, stage, position, opts);
}
}
@@ -295,7 +295,7 @@ public class DragAndDropPolicy {
@Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
+ void startIntent(PendingIntent intent, Intent fillInIntent,
@StageType int stage, @StagePosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
@@ -337,9 +337,8 @@ public class DragAndDropPolicy {
}
@Override
- public void startIntent(PendingIntent intent, Context context,
- @Nullable Intent fillInIntent, int stage, int position,
- @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int stage,
+ int position, @Nullable Bundle options) {
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index f4c0f9384705..cf35656a395b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -67,7 +67,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
final SurfaceSession mSurfaceSession = new SurfaceSession();
- private final SplitScreenTransitions mSplitTransitions;
+ private final LegacySplitScreenTransitions mSplitTransitions;
LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
ShellTaskOrganizer shellTaskOrganizer,
@@ -75,7 +75,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
SyncTransactionQueue syncQueue) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
- mSplitTransitions = new SplitScreenTransitions(splitScreenController.mTransactionPool,
+ mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool,
transitions, mSplitScreenController, this);
transitions.addHandler(mSplitTransitions);
mSyncQueue = syncQueue;
@@ -112,7 +112,7 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
return mTaskOrganizer;
}
- SplitScreenTransitions getSplitTransitions() {
+ LegacySplitScreenTransitions getSplitTransitions() {
return mSplitTransitions;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
index eea5c08818cc..27c56fd55e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.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;
@@ -46,7 +47,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
/** Plays transition animations for split-screen */
-public class SplitScreenTransitions implements Transitions.TransitionHandler {
+public class LegacySplitScreenTransitions implements Transitions.TransitionHandler {
private static final String TAG = "SplitScreenTransitions";
public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10;
@@ -67,7 +68,7 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler {
private Transitions.TransitionFinishCallback mFinishCallback = null;
private SurfaceControl.Transaction mFinishTransaction;
- SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
@NonNull LegacySplitScreenController splitScreen,
@NonNull LegacySplitScreenTaskListener listener) {
mTransactionPool = pool;
@@ -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 82468ad999b4..5a2ef568d82a 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/src/com/android/wm/shell/onehanded/IOneHanded.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
new file mode 100644
index 000000000000..008b5087d7da
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.onehanded;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the OneHanded feature.
+ */
+interface IOneHanded {
+
+ /**
+ * Enters one handed mode.
+ */
+ oneway void startOneHanded() = 1;
+
+ /**
+ * Exits one handed mode.
+ */
+ oneway void stopOneHanded() = 2;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 4f31c370108d..242f8f120e27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -26,6 +26,14 @@ import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEv
*/
@ExternalThread
public interface OneHanded {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate OneHanded.
+ */
+ default IOneHanded createExternalInterface() {
+ return null;
+ }
+
/**
* Return one handed settings enabled or not.
*/
@@ -81,4 +89,9 @@ public interface OneHanded {
* Receive onConfigurationChanged() events
*/
void onConfigChanged(Configuration newConfig);
+
+ /**
+ * Notifies when user switch complete
+ */
+ void onUserSwitch(int userId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index d90cc4769286..703eba9d6af7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -17,13 +17,12 @@
package com.android.wm.shell.onehanded;
import android.content.Context;
-import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import android.view.WindowManager;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
@@ -31,11 +30,13 @@ import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.core.content.ContextCompat;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -50,14 +51,16 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
private final Object mLock = new Object();
private final SurfaceSession mSurfaceSession = new SurfaceSession();
- private final float[] mColor;
- private final float mAlpha;
- private final Rect mRect;
+ private final float[] mDefaultColor;
private final Executor mMainExecutor;
- private final Rect mDisplaySize;
private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
+ /**
+ * The background to distinguish the boundary of translated windows and empty region when
+ * one handed mode triggered.
+ */
+ private Rect mBkgBounds;
@VisibleForTesting
@GuardedBy("mLock")
boolean mIsShowing;
@@ -82,15 +85,18 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
mMainExecutor.execute(() -> removeBackgroundPanelLayer());
}
- public OneHandedBackgroundPanelOrganizer(Context context, WindowManager windowManager,
- DisplayController displayController, Executor executor) {
+ public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
+ Executor executor) {
super(executor);
- mDisplaySize = windowManager.getCurrentWindowMetrics().getBounds();
- final Resources res = context.getResources();
- final float defaultRGB = res.getFloat(R.dimen.config_one_handed_background_rgb);
- mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
- mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
- mRect = new Rect(0, 0, mDisplaySize.width(), mDisplaySize.height());
+ // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
+ if (displayLayout.height() > displayLayout.width()) {
+ mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
+ } else {
+ mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
+ }
+ final int defaultColor = ContextCompat.getColor(context, R.color.GM2_grey_800);
+ mDefaultColor = new float[]{Color.red(defaultColor) / 255.0f,
+ Color.green(defaultColor) / 255.0f, Color.blue(defaultColor) / 255.0f};
mMainExecutor = executor;
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
}
@@ -144,9 +150,10 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
if (mBackgroundSurface == null) {
mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
.setParent(mParentLeash)
+ .setBufferSize(mBkgBounds.width(), mBkgBounds.height())
.setColorLayer()
- .setFormat(PixelFormat.RGBA_8888)
- .setOpaque(false)
+ .setFormat(PixelFormat.RGB_888)
+ .setOpaque(true)
.setName("one-handed-background-panel")
.setCallsite("OneHandedBackgroundPanelOrganizer")
.build();
@@ -170,8 +177,7 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
SurfaceControl.Transaction transaction =
mSurfaceControlTransactionFactory.getTransaction();
transaction.setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
- .setColor(mBackgroundSurface, mColor)
- .setAlpha(mBackgroundSurface, mAlpha)
+ .setColor(mBackgroundSurface, mDefaultColor)
.show(mBackgroundSurface)
.apply();
transaction.close();
@@ -188,11 +194,21 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
SurfaceControl.Transaction transaction =
mSurfaceControlTransactionFactory.getTransaction();
- transaction.remove(mBackgroundSurface);
- transaction.apply();
+ transaction.remove(mBackgroundSurface).apply();
transaction.close();
mBackgroundSurface = null;
mIsShowing = false;
}
}
+
+ void dump(@NonNull PrintWriter pw) {
+ final String innerPrefix = " ";
+ pw.println(TAG + "states: ");
+ pw.print(innerPrefix + "mIsShowing=");
+ pw.println(mIsShowing);
+ pw.print(innerPrefix + "mBkgBounds=");
+ pw.println(mBkgBounds);
+ pw.print(innerPrefix + "mDefaultColor=");
+ pw.println(mDefaultColor);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 5fc7c987899f..38cf9e6183b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -17,20 +17,25 @@
package com.android.wm.shell.onehanded;
import static android.os.UserHandle.USER_CURRENT;
+import static android.os.UserHandle.myUserId;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
+import android.annotation.BinderThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;
+import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -43,6 +48,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -54,7 +61,7 @@ import java.io.PrintWriter;
/**
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
-public class OneHandedController {
+public class OneHandedController implements RemoteCallable<OneHandedController> {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -68,11 +75,14 @@ public class OneHandedController {
private volatile boolean mIsSwipeToNotificationEnabled;
private boolean mTaskChangeToExit;
private boolean mLockedDisabled;
+ private int mUserId;
private float mOffSetFraction;
- private final Context mContext;
+ private Context mContext;
+
+ private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
- private final OneHandedGestureHandler mGestureHandler;
+ private final OneHandedSettingsUtil mOneHandedSettingsUtil;
private final OneHandedTimeoutHandler mTimeoutHandler;
private final OneHandedTouchHandler mTouchHandler;
private final OneHandedTutorialHandler mTutorialHandler;
@@ -82,10 +92,9 @@ public class OneHandedController {
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final OneHandedImpl mImpl = new OneHandedImpl();
- private final WindowManager mWindowManager;
private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
- private final AccessibilityManager mAccessibilityManager;
+ private OneHandedGestureHandler mGestureHandler;
private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
/**
@@ -93,8 +102,29 @@ public class OneHandedController {
*/
private final DisplayChangeController.OnDisplayChangingListener mRotationController =
(display, fromRotation, toRotation, wct) -> {
- if (mDisplayAreaOrganizer != null) {
- mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation, wct);
+ if (!isInitialized()) {
+ return;
+ }
+ mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
+ mGestureHandler.onRotateDisplay(mDisplayAreaOrganizer.getDisplayLayout());
+ };
+
+ private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
+ return;
+ }
+ updateDisplayLayout(displayId);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != DEFAULT_DISPLAY || !isInitialized()) {
+ return;
+ }
+ updateDisplayLayout(displayId);
}
};
@@ -108,17 +138,22 @@ public class OneHandedController {
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
public void onAccessibilityStateChanged(boolean enabled) {
+ if (!isInitialized()) {
+ return;
+ }
if (enabled) {
- final int mOneHandedTimeout = OneHandedSettingsUtil
- .getSettingsOneHandedModeTimeout(mContext.getContentResolver());
+ final int mOneHandedTimeout = mOneHandedSettingsUtil
+ .getSettingsOneHandedModeTimeout(
+ mContext.getContentResolver(), mUserId);
final int timeout = mAccessibilityManager
.getRecommendedTimeoutMillis(mOneHandedTimeout * 1000
/* align with A11y timeout millis */,
AccessibilityManager.FLAG_CONTENT_CONTROLS);
mTimeoutHandler.setTimeout(timeout / 1000);
} else {
- mTimeoutHandler.setTimeout(OneHandedSettingsUtil
- .getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
+ mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
+ .getSettingsOneHandedModeTimeout(
+ mContext.getContentResolver(), mUserId));
}
}
};
@@ -136,6 +171,14 @@ public class OneHandedController {
}
};
+ private boolean isInitialized() {
+ if (mDisplayAreaOrganizer == null || mDisplayController == null
+ || mGestureHandler == null || mOneHandedSettingsUtil == null) {
+ Slog.w(TAG, "Components may not initialized yet!");
+ return false;
+ }
+ return true;
+ }
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
@@ -143,8 +186,8 @@ public class OneHandedController {
@Nullable
public static OneHandedController create(
Context context, WindowManager windowManager, DisplayController displayController,
- TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
- ShellExecutor mainExecutor, Handler mainHandler) {
+ DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) {
if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
Slog.w(TAG, "Device doesn't support OneHanded feature");
return null;
@@ -158,32 +201,31 @@ public class OneHandedController {
OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
mainExecutor);
OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
- context, windowManager, displayController, ViewConfiguration.get(context),
- mainExecutor);
+ context, displayLayout, ViewConfiguration.get(context), mainExecutor);
OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
- new OneHandedBackgroundPanelOrganizer(context, windowManager, displayController,
- mainExecutor);
+ new OneHandedBackgroundPanelOrganizer(context, displayLayout, mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
- context, windowManager, displayController, animationController, tutorialHandler,
+ context, displayLayout, animationController, tutorialHandler,
oneHandedBackgroundPanelOrganizer, mainExecutor);
+ OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
- return new OneHandedController(context, windowManager, displayController,
+ return new OneHandedController(context, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
- gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
- taskStackListener, mainExecutor, mainHandler);
+ gestureHandler, settingsUtil, timeoutHandler, oneHandedUiEventsLogger,
+ overlayManager, taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
- WindowManager windowManager,
DisplayController displayController,
OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
OneHandedTouchHandler touchHandler,
OneHandedTutorialHandler tutorialHandler,
OneHandedGestureHandler gestureHandler,
+ OneHandedSettingsUtil settingsUtil,
OneHandedTimeoutHandler timeoutHandler,
OneHandedUiEventLogger uiEventsLogger,
IOverlayManager overlayManager,
@@ -191,7 +233,7 @@ public class OneHandedController {
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
- mWindowManager = windowManager;
+ mOneHandedSettingsUtil = settingsUtil;
mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
mDisplayController = displayController;
@@ -204,16 +246,18 @@ public class OneHandedController {
mOneHandedUiEventLogger = uiEventsLogger;
mTaskStackListener = taskStackListener;
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+ mUserId = myUserId();
mOffSetFraction = sysPropPercentageConfig / 100.0f;
- mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- context.getContentResolver());
+ mIsOneHandedEnabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ context.getContentResolver(), mUserId);
mIsSwipeToNotificationEnabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- context.getContentResolver());
+ mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ context.getContentResolver(), mUserId);
mTimeoutHandler = timeoutHandler;
mEnabledObserver = getObserver(this::onEnabledSettingChanged);
@@ -223,9 +267,8 @@ public class OneHandedController {
getObserver(this::onSwipeToNotificationEnabledSettingChanged);
mDisplayController.addDisplayChangingController(mRotationController);
-
setupCallback();
- setupSettingObservers();
+ registerSettingObservers(mUserId);
setupTimeoutListener();
setupGesturalOverlay();
updateSettings();
@@ -239,6 +282,16 @@ public class OneHandedController {
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
/**
* Set one handed enabled or disabled when user update settings
*/
@@ -273,8 +326,14 @@ public class OneHandedController {
Slog.d(TAG, "Temporary lock disabled");
return;
}
+ final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation();
+ if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) {
+ Slog.w(TAG, "One handed mode only support portrait mode");
+ return;
+ }
if (!mDisplayAreaOrganizer.isInOneHanded()) {
- final int yOffSet = Math.round(getDisplaySize().height() * mOffSetFraction);
+ final int yOffSet = Math.round(
+ mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
@@ -324,27 +383,44 @@ public class OneHandedController {
}
}
- private void setupSettingObservers() {
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
- mContext.getContentResolver(), mEnabledObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
- mContext.getContentResolver(), mTimeoutObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
- mContext.getContentResolver(), mTaskChangeExitObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(
+ private void registerSettingObservers(int newUserId) {
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
+ mContext.getContentResolver(), mEnabledObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
+ mContext.getContentResolver(), mTimeoutObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
+ mContext.getContentResolver(), mTaskChangeExitObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
- mContext.getContentResolver(), mSwipeToNotificationEnabledObserver);
+ mContext.getContentResolver(), mSwipeToNotificationEnabledObserver, newUserId);
+ }
+
+ private void unregisterSettingObservers() {
+ mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
+ mEnabledObserver);
+ mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
+ mTimeoutObserver);
+ mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
+ mTaskChangeExitObserver);
+ mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
+ mSwipeToNotificationEnabledObserver);
}
private void updateSettings() {
- setOneHandedEnabled(OneHandedSettingsUtil
- .getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
- mTimeoutHandler.setTimeout(OneHandedSettingsUtil
- .getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
- setTaskChangeToExit(OneHandedSettingsUtil
- .getSettingsTapsAppToExit(mContext.getContentResolver()));
- setSwipeToNotificationEnabled(OneHandedSettingsUtil
- .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
+ setOneHandedEnabled(mOneHandedSettingsUtil
+ .getSettingsOneHandedModeEnabled(mContext.getContentResolver(), mUserId));
+ mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
+ .getSettingsOneHandedModeTimeout(mContext.getContentResolver(), mUserId));
+ setTaskChangeToExit(mOneHandedSettingsUtil
+ .getSettingsTapsAppToExit(mContext.getContentResolver(), mUserId));
+ setSwipeToNotificationEnabled(mOneHandedSettingsUtil
+ .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver(), mUserId));
+ }
+
+ private void updateDisplayLayout(int displayId) {
+ final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
+ mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
+ mGestureHandler.onDisplayChanged(newDisplayLayout);
}
private ContentObserver getObserver(Runnable onChangeRunnable) {
@@ -358,8 +434,8 @@ public class OneHandedController {
@VisibleForTesting
void onEnabledSettingChanged() {
- final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContext.getContentResolver());
+ final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver(), mUserId);
mOneHandedUiEventLogger.writeEvent(enabled
? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
: OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_OFF);
@@ -368,14 +444,14 @@ public class OneHandedController {
// Also checks swipe to notification settings since they all need gesture overlay.
setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContext.getContentResolver()));
+ enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mContext.getContentResolver(), mUserId));
}
@VisibleForTesting
void onTimeoutSettingChanged() {
- final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
- mContext.getContentResolver());
+ final int newTimeout = mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
+ mContext.getContentResolver(), mUserId);
int metricsId = OneHandedUiEventLogger.OneHandedSettingsTogglesEvent.INVALID.getId();
switch (newTimeout) {
case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER:
@@ -403,8 +479,8 @@ public class OneHandedController {
@VisibleForTesting
void onTaskChangeExitSettingChanged() {
- final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
- mContext.getContentResolver());
+ final boolean enabled = mOneHandedSettingsUtil.getSettingsTapsAppToExit(
+ mContext.getContentResolver(), mUserId);
mOneHandedUiEventLogger.writeEvent(enabled
? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
: OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF);
@@ -415,14 +491,14 @@ public class OneHandedController {
@VisibleForTesting
void onSwipeToNotificationEnabledSettingChanged() {
final boolean enabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContext.getContentResolver());
+ mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mContext.getContentResolver(), mUserId);
setSwipeToNotificationEnabled(enabled);
// Also checks one handed mode settings since they all need gesture overlay.
setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContext.getContentResolver()));
+ enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver(), mUserId));
}
private void setupTimeoutListener() {
@@ -430,24 +506,6 @@ public class OneHandedController {
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_TIMEOUT_OUT));
}
- /**
- * Query the current display real size from {@link WindowManager}
- *
- * @return {@link WindowManager#getCurrentWindowMetrics()#getBounds()}
- */
- private Rect getDisplaySize() {
- if (mWindowManager == null) {
- Slog.e(TAG, "WindowManager instance is null! Can not get display size!");
- return new Rect();
- }
- final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
- if (displaySize.width() == 0 || displaySize.height() == 0) {
- Slog.e(TAG, "Display size error! width = " + displaySize.width()
- + ", height = " + displaySize.height());
- }
- return displaySize;
- }
-
@VisibleForTesting
boolean isLockedDisabled() {
return mLockedDisabled;
@@ -459,7 +517,7 @@ public class OneHandedController {
}
mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
- mGestureHandler.onOneHandedEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
+ mGestureHandler.onGestureEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);
if (!mIsOneHandedEnabled) {
mDisplayAreaOrganizer.unregisterOrganizer();
@@ -480,7 +538,8 @@ public class OneHandedController {
}
private void setupGesturalOverlay() {
- if (!OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver())) {
+ if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver(), mUserId)) {
return;
}
@@ -507,10 +566,15 @@ public class OneHandedController {
@VisibleForTesting
void setLockedDisabled(boolean locked, boolean enabled) {
- if (enabled == mIsOneHandedEnabled) {
+ final boolean isFeatureEnabled = mIsOneHandedEnabled || mIsSwipeToNotificationEnabled;
+
+ if (enabled == isFeatureEnabled) {
return;
}
mLockedDisabled = locked && !enabled;
+
+ // Disabled gesture when keyguard ON
+ mGestureHandler.onGestureEnabled(!mLockedDisabled && isFeatureEnabled);
}
private void onConfigChanged(Configuration newConfig) {
@@ -523,6 +587,14 @@ public class OneHandedController {
}
}
+ private void onUserSwitch(int newUserId) {
+ unregisterSettingObservers();
+ mUserId = newUserId;
+ registerSettingObservers(newUserId);
+ updateSettings();
+ updateOneHandedEnabled();
+ }
+
public void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "States: ");
@@ -530,6 +602,12 @@ public class OneHandedController {
pw.println(mOffSetFraction);
pw.print(innerPrefix + "mLockedDisabled=");
pw.println(mLockedDisabled);
+ pw.print(innerPrefix + "mUserId=");
+ pw.println(mUserId);
+
+ if (mBackgroundPanelOrganizer != null) {
+ mBackgroundPanelOrganizer.dump(pw);
+ }
if (mDisplayAreaOrganizer != null) {
mDisplayAreaOrganizer.dump(pw);
@@ -551,7 +629,7 @@ public class OneHandedController {
mTutorialHandler.dump(pw);
}
- OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver());
+ mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver(), mUserId);
if (mOverlayManager != null) {
OverlayInfo info = null;
@@ -567,8 +645,22 @@ public class OneHandedController {
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
@ExternalThread
private class OneHandedImpl implements OneHanded {
+ private IOneHandedImpl mIOneHanded;
+
+ @Override
+ public IOneHanded createExternalInterface() {
+ if (mIOneHanded != null) {
+ mIOneHanded.invalidate();
+ }
+ mIOneHanded = new IOneHandedImpl(OneHandedController.this);
+ return mIOneHanded;
+ }
+
@Override
public boolean isOneHandedEnabled() {
// This is volatile so return directly
@@ -636,5 +728,47 @@ public class OneHandedController {
OneHandedController.this.onConfigChanged(newConfig);
});
}
+
+ @Override
+ public void onUserSwitch(int userId) {
+ mMainExecutor.execute(() -> {
+ OneHandedController.this.onUserSwitch(userId);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IOneHandedImpl extends IOneHanded.Stub {
+ private OneHandedController mController;
+
+ IOneHandedImpl(OneHandedController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void startOneHanded() {
+ executeRemoteCallWithTaskPermission(mController, "startOneHanded",
+ (controller) -> {
+ controller.startOneHanded();
+ });
+ }
+
+ @Override
+ public void stopOneHanded() {
+ executeRemoteCallWithTaskPermission(mController, "stopOneHanded",
+ (controller) -> {
+ controller.stopOneHanded();
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 0238fa8a7936..682c9a3f0d62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -23,9 +23,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.ArrayMap;
-import android.util.Slog;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
@@ -37,7 +35,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
@@ -59,7 +57,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
"persist.debug.one_handed_translate_animation_duration";
- private final WindowManager mWindowManager;
+ private DisplayLayout mDisplayLayout = new DisplayLayout();
+
private final Rect mLastVisualDisplayBounds = new Rect();
private final Rect mDefaultDisplayBounds = new Rect();
@@ -67,7 +66,6 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private int mEnterExitAnimationDurationMs;
private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap();
- private DisplayController mDisplayController;
private OneHandedAnimationController mAnimationController;
private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -110,17 +108,15 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
* Constructor of OneHandedDisplayAreaOrganizer
*/
public OneHandedDisplayAreaOrganizer(Context context,
- WindowManager windowManager,
- DisplayController displayController,
+ DisplayLayout displayLayout,
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
ShellExecutor mainExecutor) {
super(mainExecutor);
- mWindowManager = windowManager;
+ mDisplayLayout.set(displayLayout);
+ updateDisplayBounds();
mAnimationController = animationController;
- mDisplayController = displayController;
- mLastVisualDisplayBounds.set(getDisplayBounds());
final int animationDurationConfig = context.getResources().getInteger(
R.integer.config_one_handed_translate_animation_duration);
mEnterExitAnimationDurationMs =
@@ -150,7 +146,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
}
- mDefaultDisplayBounds.set(getDisplayBounds());
+ updateDisplayBounds();
return displayAreaInfos;
}
@@ -161,25 +157,21 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
/**
- * Handler for display rotation changes by below policy which
- * handles 90 degree display rotation changes {@link Surface.Rotation}.
+ * Handler for display rotation changes by {@link DisplayLayout}
*
- * @param fromRotation starting rotation of the display.
- * @param toRotation target rotation of the display (after rotating).
- * @param wct A task transaction {@link WindowContainerTransaction} from
- * {@link DisplayChangeController} to populate.
+ * @param context Any context
+ * @param toRotation target rotation of the display (after rotating).
+ * @param wct A task transaction {@link WindowContainerTransaction} from
+ * {@link DisplayChangeController} to populate.
*/
- public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
- // Stop one handed without animation and reset cropped size immediately
- final Rect newBounds = new Rect(mDefaultDisplayBounds);
- final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
-
- if (isOrientationDiff) {
- resetWindowsOffset(wct);
- mDefaultDisplayBounds.set(newBounds);
- mLastVisualDisplayBounds.set(newBounds);
- finishOffset(0, TRANSITION_DIRECTION_EXIT);
+ public void onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct) {
+ if (mDisplayLayout.rotation() == toRotation) {
+ return;
}
+ mDisplayLayout.rotateTo(context.getResources(), toRotation);
+ resetWindowsOffset(wct);
+ updateDisplayBounds();
+ finishOffset(0, TRANSITION_DIRECTION_EXIT);
}
/**
@@ -191,9 +183,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
mDefaultDisplayBounds.top + yOffset,
mDefaultDisplayBounds.right,
mDefaultDisplayBounds.bottom + yOffset);
- final Rect fromBounds = getLastVisualDisplayBounds() != null
- ? getLastVisualDisplayBounds()
- : mDefaultDisplayBounds;
+ final Rect fromBounds = getLastVisualDisplayBounds();
final int direction = yOffset > 0
? TRANSITION_DIRECTION_TRIGGER
: TRANSITION_DIRECTION_EXIT;
@@ -219,7 +209,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
applyTransaction(wct);
}
- private void resetWindowsOffset(WindowContainerTransaction wct) {
+ @VisibleForTesting
+ void resetWindowsOffset(WindowContainerTransaction wct) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
mDisplayAreaTokenMap.forEach(
@@ -292,18 +283,19 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mLastVisualDisplayBounds;
}
+ @VisibleForTesting
@Nullable
- private Rect getDisplayBounds() {
- if (mWindowManager == null) {
- Slog.e(TAG, "WindowManager instance is null! Can not get display size!");
- return new Rect();
- }
- final Rect displayBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- if (displayBounds.width() == 0 || displayBounds.height() == 0) {
- Slog.e(TAG, "Display size error! width = " + displayBounds.width()
- + ", height = " + displayBounds.height());
- }
- return displayBounds;
+ Rect getLastDisplayBounds() {
+ return mLastVisualDisplayBounds;
+ }
+
+ public DisplayLayout getDisplayLayout() {
+ return mDisplayLayout;
+ }
+
+ @VisibleForTesting
+ void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
}
@VisibleForTesting
@@ -311,6 +303,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreaTokenMap;
}
+ void updateDisplayBounds() {
+ mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ mLastVisualDisplayBounds.set(mDefaultDisplayBounds);
+ }
+
/**
* Register transition callback
*/
@@ -323,13 +320,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mIsInOneHanded=");
pw.println(mIsInOneHanded);
+ pw.print(innerPrefix + "mDisplayLayout.rotation()=");
+ pw.println(mDisplayLayout.rotation());
pw.print(innerPrefix + "mDisplayAreaTokenMap=");
pw.println(mDisplayAreaTokenMap);
pw.print(innerPrefix + "mDefaultDisplayBounds=");
pw.println(mDefaultDisplayBounds);
pw.print(innerPrefix + "mLastVisualDisplayBounds=");
pw.println(mLastVisualDisplayBounds);
- pw.print(innerPrefix + "getDisplayBounds()=");
- pw.println(getDisplayBounds());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 778876c76afe..9e83a61667b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -24,7 +24,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Looper;
-import android.util.Log;
import android.view.Display;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -33,29 +32,23 @@ import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayChangeController;
-import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
/**
- * The class manage swipe up and down gesture for 3-Button mode navigation,
- * others(e.g, 2-button, full gesture mode) are handled by Launcher quick steps.
- * TODO(b/160934654) Migrate to Launcher quick steps
+ * The class manage swipe up and down gesture for 3-Button mode navigation, others(e.g, 2-button,
+ * full gesture mode) are handled by Launcher quick steps. TODO(b/160934654) Migrate to Launcher
+ * quick steps
*/
-public class OneHandedGestureHandler implements OneHandedTransitionCallback,
- DisplayChangeController.OnDisplayChangingListener {
+public class OneHandedGestureHandler implements OneHandedTransitionCallback {
private static final String TAG = "OneHandedGestureHandler";
- private static final boolean DEBUG_GESTURE = false;
private static final int ANGLE_MAX = 150;
private static final int ANGLE_MIN = 30;
@@ -64,7 +57,6 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final PointF mStartDragPos = new PointF();
- private final WindowManager mWindowManager;
private boolean mPassedSlop;
private boolean mAllowGesture;
@@ -85,37 +77,35 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
private boolean mIsStopGesture;
/**
- * Constructor of OneHandedGestureHandler, we only handle the gesture of
- * {@link Display#DEFAULT_DISPLAY}
+ * Constructor of OneHandedGestureHandler, we only handle the gesture of {@link
+ * Display#DEFAULT_DISPLAY}
*
- * @param context {@link Context}
- * @param displayController {@link DisplayController}
+ * @param context Any context
+ * @param displayLayout Current {@link DisplayLayout} from controller
+ * @param viewConfig {@link ViewConfiguration} to obtain touch slop
+ * @param mainExecutor The wm-shell main executor
*/
- public OneHandedGestureHandler(Context context, WindowManager windowManager,
- DisplayController displayController, ViewConfiguration viewConfig,
+ public OneHandedGestureHandler(Context context,
+ DisplayLayout displayLayout,
+ ViewConfiguration viewConfig,
ShellExecutor mainExecutor) {
- mWindowManager = windowManager;
mMainExecutor = mainExecutor;
- displayController.addDisplayChangingController(this);
- mNavGestureHeight = getNavBarSize(context,
- displayController.getDisplayLayout(DEFAULT_DISPLAY));
mDragDistThreshold = context.getResources().getDimensionPixelSize(
R.dimen.gestures_onehanded_drag_threshold);
+
final float slop = viewConfig.getScaledTouchSlop();
mSquaredSlop = slop * slop;
-
+ onDisplayChanged(displayLayout);
updateIsEnabled();
}
/**
- * Notified by {@link OneHandedController}, when user update settings of Enabled or Disabled
+ * Notifies by {@link OneHandedController}, when swipe down gesture is enabled on 3 button
+ * navigation bar mode.
*
- * @param isEnabled is one handed settings enabled or not
+ * @param isEnabled Either one handed mode or swipe for notification function enabled or not
*/
- public void onOneHandedEnabled(boolean isEnabled) {
- if (DEBUG_GESTURE) {
- Log.d(TAG, "onOneHandedEnabled, isEnabled = " + isEnabled);
- }
+ public void onGestureEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
updateIsEnabled();
}
@@ -126,25 +116,31 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
/**
- * Register {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
+ * Registers {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
*/
public void setGestureEventListener(OneHandedGestureEventCallback callback) {
mGestureEventCallback = callback;
}
+ /**
+ * Called when onDisplayAdded() or onDisplayRemoved() callback
+ * @param displayLayout The latest {@link DisplayLayout} representing current displayId
+ */
+ public void onDisplayChanged(DisplayLayout displayLayout) {
+ mNavGestureHeight = getNavBarSize(displayLayout);
+ mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
+ displayLayout.height());
+ mRotation = displayLayout.rotation();
+ }
+
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
- mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY())
- && mRotation == Surface.ROTATION_0;
+ mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) && isGestureAvailable();
if (mAllowGesture) {
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
}
- if (DEBUG_GESTURE) {
- Log.d(TAG, "ACTION_DOWN, mDownPos=" + mDownPos + ", mAllowGesture="
- + mAllowGesture);
- }
} else if (mAllowGesture) {
switch (action) {
case MotionEvent.ACTION_MOVE:
@@ -204,34 +200,17 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
private boolean isWithinTouchRegion(float x, float y) {
- if (DEBUG_GESTURE) {
- Log.d(TAG, "isWithinTouchRegion(), mGestureRegion=" + mGestureRegion + ", downX=" + x
- + ", downY=" + y);
- }
return mGestureRegion.contains(Math.round(x), Math.round(y));
}
- private int getNavBarSize(Context context, @Nullable DisplayLayout displayLayout) {
- if (displayLayout != null) {
- return displayLayout.navBarFrameHeight();
- } else {
- return isRotated()
- ? context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height_landscape)
- : context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height);
- }
+ private int getNavBarSize(@NonNull DisplayLayout displayLayout) {
+ return isGestureAvailable() ? displayLayout.navBarFrameHeight() : 0 /* In landscape */;
}
private void updateIsEnabled() {
disposeInputChannel();
- // Either OHM or swipe notification shade can activate in portrait mode only
- if (mIsEnabled && mIsThreeButtonModeEnabled && !isRotated()) {
- final Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
- // Register input event receiver to monitor the touch region of NavBar gesture height
- mGestureRegion.set(0, displaySize.height() - mNavGestureHeight, displaySize.width(),
- displaySize.height());
+ if (mIsEnabled && mIsThreeButtonModeEnabled && isGestureAvailable()) {
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"onehanded-gesture-offset", DEFAULT_DISPLAY);
try {
@@ -251,10 +230,16 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
}
- @Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction t) {
- mRotation = toRotation;
+ /**
+ * Handler for display rotation changes by {@link DisplayLayout}
+ *
+ * @param displayLayout The rotated displayLayout
+ */
+ public void onRotateDisplay(DisplayLayout displayLayout) {
+ mRotation = displayLayout.rotation();
+ mNavGestureHeight = getNavBarSize(displayLayout);
+ mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
+ displayLayout.height());
updateIsEnabled();
}
@@ -270,8 +255,9 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
}
}
- private boolean isRotated() {
- return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+ private boolean isGestureAvailable() {
+ // Either OHM or swipe notification shade can activate in portrait mode only
+ return mRotation == Surface.ROTATION_0 || mRotation == Surface.ROTATION_180;
}
private boolean isValidStartAngle(float deltaX, float deltaY) {
@@ -291,14 +277,18 @@ public class OneHandedGestureHandler implements OneHandedTransitionCallback,
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "States: ");
+ pw.print(innerPrefix + "mAllowGesture=");
+ pw.println(mAllowGesture);
pw.print(innerPrefix + "mIsEnabled=");
pw.println(mIsEnabled);
+ pw.print(innerPrefix + "mGestureRegion=");
+ pw.println(mGestureRegion);
pw.print(innerPrefix + "mNavGestureHeight=");
pw.println(mNavGestureHeight);
pw.print(innerPrefix + "mIsThreeButtonModeEnabled=");
pw.println(mIsThreeButtonModeEnabled);
- pw.print(innerPrefix + "isLandscape=");
- pw.println(isRotated());
+ pw.print(innerPrefix + "mRotation=");
+ pw.println(mRotation);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index f8217c64e53d..1b2fcdd6313e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -22,6 +22,8 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Settings;
+import androidx.annotation.Nullable;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -62,17 +64,19 @@ public final class OneHandedSettingsUtil {
/**
* Register one handed preference settings observer
*
- * @param key Setting key to monitor in observer
- * @param resolver ContentResolver of context
- * @param observer Observer from caller
+ * @param key Setting key to monitor in observer
+ * @param resolver ContentResolver of context
+ * @param observer Observer from caller
+ * @param newUserId New user id to be registered
* @return uri key for observing
*/
- public static Uri registerSettingsKeyObserver(String key, ContentResolver resolver,
- ContentObserver observer) {
+ @Nullable
+ public Uri registerSettingsKeyObserver(String key, ContentResolver resolver,
+ ContentObserver observer, int newUserId) {
Uri uriKey = null;
uriKey = Settings.Secure.getUriFor(key);
if (resolver != null && uriKey != null) {
- resolver.registerContentObserver(uriKey, false, observer);
+ resolver.registerContentObserver(uriKey, false, observer, newUserId);
}
return uriKey;
}
@@ -80,10 +84,10 @@ public final class OneHandedSettingsUtil {
/**
* Unregister one handed preference settings observer
*
- * @param resolver ContentResolver of context
- * @param observer preference key change observer
+ * @param resolver ContentResolver of context
+ * @param observer preference key change observer
*/
- public static void unregisterSettingsKeyObserver(ContentResolver resolver,
+ public void unregisterSettingsKeyObserver(ContentResolver resolver,
ContentObserver observer) {
if (resolver != null) {
resolver.unregisterContentObserver(observer);
@@ -95,9 +99,9 @@ public final class OneHandedSettingsUtil {
*
* @return enable or disable one handed mode flag.
*/
- public static boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) {
- return Settings.Secure.getInt(resolver,
- Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */) == 1;
+ public boolean getSettingsOneHandedModeEnabled(ContentResolver resolver, int userId) {
+ return Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */, userId) == 1;
}
/**
@@ -105,40 +109,44 @@ public final class OneHandedSettingsUtil {
*
* @return enable or disable taps app exit.
*/
- public static boolean getSettingsTapsAppToExit(ContentResolver resolver) {
- return Settings.Secure.getInt(resolver,
- Settings.Secure.TAPS_APP_TO_EXIT, 0) == 1;
+ public boolean getSettingsTapsAppToExit(ContentResolver resolver, int userId) {
+ return Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.TAPS_APP_TO_EXIT, 0, userId) == 1;
}
/**
- * Query timeout value from Settings provider.
- * Default is {@link OneHandedSettingsUtil#ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS}
+ * Query timeout value from Settings provider. Default is
+ * {@link OneHandedSettingsUtil#ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS}
*
* @return timeout value in seconds.
*/
- public static @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) {
- return Settings.Secure.getInt(resolver,
- Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
+ public @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver,
+ int userId) {
+ return Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS,
+ userId);
}
/**
* Returns whether swipe bottom to notification gesture enabled or not.
*/
- public static boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
+ public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver, int userId) {
return Settings.Secure.getInt(resolver,
- Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
+ Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1;
}
- protected static void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
+ void dump(PrintWriter pw, String prefix, ContentResolver resolver,
+ int userId) {
final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
+ pw.println(innerPrefix + TAG);
pw.print(innerPrefix + "isOneHandedModeEnable=");
- pw.println(getSettingsOneHandedModeEnabled(resolver));
+ pw.println(getSettingsOneHandedModeEnabled(resolver, userId));
pw.print(innerPrefix + "oneHandedTimeOut=");
- pw.println(getSettingsOneHandedModeTimeout(resolver));
+ pw.println(getSettingsOneHandedModeTimeout(resolver, userId));
pw.print(innerPrefix + "tapsAppToExit=");
- pw.println(getSettingsTapsAppToExit(resolver));
+ pw.println(getSettingsTapsAppToExit(resolver, userId));
}
- private OneHandedSettingsUtil() {}
+ public OneHandedSettingsUtil() {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
new file mode 100644
index 000000000000..a6ffa6e44584
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -0,0 +1,63 @@
+/*
+ * 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.pip;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+
+import com.android.wm.shell.pip.IPipAnimationListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the Pip feature.
+ */
+interface IPip {
+
+ /**
+ * Notifies that Activity is about to be swiped to home with entering PiP transition and
+ * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param activityInfo ActivityInfo tied to the Activity
+ * @param pictureInPictureParams PictureInPictureParams tied to the Activity
+ * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
+ * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
+ * @return destination bounds the PiP window should land into
+ */
+ Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
+ in PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) = 1;
+
+ /**
+ * Notifies the swiping Activity to PiP onto home transition is finished
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param destinationBounds the destination bounds the PiP window lands into
+ */
+ oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2;
+
+ /**
+ * Sets listener to get pinned stack animation callbacks.
+ */
+ oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+
+ /**
+ * Sets the shelf height and visibility.
+ */
+ oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
new file mode 100644
index 000000000000..2569b780c1bb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.pip;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks.
+ */
+oneway interface IPipAnimationListener {
+ /**
+ * Notifies the listener that the Pip animation is started.
+ */
+ void onPipAnimationStarted();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index d14c3e3c0dd4..6d4773bdeb1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,15 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.Nullable;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -34,6 +29,14 @@ import java.util.function.Consumer;
*/
@ExternalThread
public interface Pip {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate PIP.
+ */
+ default IPip createExternalInterface() {
+ return null;
+ }
+
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
@@ -109,30 +112,6 @@ public interface Pip {
default void showPictureInPictureMenu() {}
/**
- * Called by Launcher when swiping an auto-pip enabled Activity to home starts
- * @param componentName {@link ComponentName} represents the Activity entering PiP
- * @param activityInfo {@link ActivityInfo} tied to the Activity
- * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity
- * @param launcherRotation Rotation Launcher is in
- * @param shelfHeight Shelf height when landing PiP window onto Launcher
- * @return Destination bounds of PiP window based on the parameters passed in
- */
- default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) {
- return null;
- }
-
- /**
- * Called by Launcher when swiping an auto-pip enable Activity to home finishes
- * @param componentName {@link ComponentName} represents the Activity entering PiP
- * @param destinationBounds Destination bounds of PiP window
- */
- default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- return;
- }
-
- /**
* Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used
* for times where the PiP bounds could conflict with SystemUI elements, such as a stashed
* PiP and the Back-from-Edge gesture.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index a52db24aa184..af4ccadae538 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -540,9 +540,10 @@ public class PipAnimationController {
// WindowContainerTransaction in task organizer
final Rect destBounds = getDestinationBounds();
getSurfaceTransactionHelper().resetScale(tx, leash, destBounds);
- if (transitionDirection == TRANSITION_DIRECTION_LEAVE_PIP) {
- // Leaving to fullscreen, reset crop to null.
- tx.setPosition(leash, destBounds.left, destBounds.top);
+ if (isOutPipDirection(transitionDirection)) {
+ // Exit pip, clear scale, position and crop.
+ tx.setMatrix(leash, 1, 0, 0, 1);
+ tx.setPosition(leash, 0, 0);
tx.setWindowCrop(leash, 0, 0);
} else {
getSurfaceTransactionHelper().crop(tx, leash, destBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index cb39b4e63655..e3594d0cd367 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -35,6 +35,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Singleton source of truth for the current state of PIP bounds.
@@ -84,6 +85,7 @@ public final class PipBoundsState {
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
+ private @Nullable Consumer<Rect> mOnPipExclusionBoundsChangeCallback;
public PipBoundsState(@NonNull Context context) {
mContext = context;
@@ -102,6 +104,9 @@ public final class PipBoundsState {
/** Set the current PIP bounds. */
public void setBounds(@NonNull Rect bounds) {
mBounds.set(bounds);
+ if (mOnPipExclusionBoundsChangeCallback != null) {
+ mOnPipExclusionBoundsChangeCallback.accept(bounds);
+ }
}
/** Get the current PIP bounds. */
@@ -386,6 +391,18 @@ public final class PipBoundsState {
mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
}
+ /**
+ * Set a callback to watch out for PiP bounds. This is mostly used by SystemUI's
+ * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
+ */
+ public void setPipExclusionBoundsChangeCallback(
+ @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
+ mOnPipExclusionBoundsChangeCallback = onPipExclusionBoundsChangeCallback;
+ if (mOnPipExclusionBoundsChangeCallback != null) {
+ mOnPipExclusionBoundsChangeCallback.accept(getBounds());
+ }
+ }
+
/** Source of truth for the current bounds of PIP that may be in motion. */
public static class MotionBoundsState {
/** The bounds used when PIP is in motion (e.g. during a drag or animation) */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 97aeda4b053f..582ff2180c83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -94,11 +94,14 @@ public class PipSurfaceTransactionHelper {
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds, float degrees) {
mTmpSourceRectF.set(sourceBounds);
+ // We want the matrix to position the surface relative to the screen coordinates so offset
+ // the source to 0,0
+ mTmpSourceRectF.offsetTo(0, 0);
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- mTmpTransform.postRotate(degrees);
- tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
- .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+ mTmpTransform.postRotate(degrees,
+ mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY());
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9);
return this;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 9ec7c0d173dd..99ec10049340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -41,6 +41,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PictureInPictureParams;
@@ -62,6 +63,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -130,7 +132,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipAnimationController mPipAnimationController;
private final PipTransitionController mPipTransitionController;
private final PipUiEventLogger mPipUiEventLoggerLogger;
- private final int mEnterExitAnimationDuration;
+ private final int mEnterAnimationDuration;
+ private final int mExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<LegacySplitScreenController> mSplitScreenOptional;
protected final ShellTaskOrganizer mTaskOrganizer;
@@ -224,8 +227,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
- mEnterExitAnimationDuration = context.getResources()
- .getInteger(R.integer.config_pipResizeAnimationDuration);
+ mEnterAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipEnterAnimationDuration);
+ mExitAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipExitAnimationDuration);
mSurfaceTransactionHelper = surfaceTransactionHelper;
mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
@@ -377,12 +382,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
// removePipImmediately is expected when the following animation finishes.
- mPipAnimationController
+ ValueAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f)
.setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(mEnterExitAnimationDuration)
- .start();
+ .setPipAnimationCallback(mPipAnimationCallback);
+ animator.setDuration(mExitAnimationDuration);
+ animator.setInterpolator(Interpolators.ALPHA_OUT);
+ animator.start();
mState = State.EXITING_PIP;
}
@@ -423,12 +429,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (mInSwipePipToHomeTransition) {
final Rect destinationBounds = mPipBoundsState.getBounds();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds);
+ mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds);
// animation is finished in the Launcher and here we directly apply the final touch.
applyEnterPipSyncTransaction(destinationBounds, () -> {
// ensure menu's settled in its final bounds first
finishResizeForMenu(destinationBounds);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
- });
+ }, tx);
mInSwipePipToHomeTransition = false;
return;
}
@@ -460,11 +470,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
info.pictureInPictureParams, currentBounds);
scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
- sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
+ sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
null /* updateBoundsCallback */);
mState = State.ENTERING_PIP;
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration);
+ enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
@@ -490,16 +500,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// mState is set right after the animation is kicked off to block any resize
// requests such as offsetPip that may have been called prior to the transition.
mState = State.ENTERING_PIP;
- });
+ }, null /* boundsChangeTransaction */);
}
- private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable) {
+ private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
+ @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
// PiP menu is attached late in the process here to avoid any artifacts on the leash
// caused by addShellRoot when in gesture navigation mode.
mPipMenuController.attach(mLeash);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
+ if (boundsChangeTransaction != null) {
+ wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction);
+ }
wct.scheduleFinishEnterPip(mToken, destinationBounds);
mSyncTransactionQueue.queue(wct);
if (runnable != null) {
@@ -544,7 +558,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
- if (!mState.isInPip()) {
+ if (mState == State.UNDEFINED) {
return;
}
final WindowContainerToken token = info.token;
@@ -557,6 +571,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mInSwipePipToHomeTransition = false;
mPictureInPictureParams = null;
mState = State.UNDEFINED;
+ // Re-set the PIP bounds to none.
+ mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
mPipMenuController.detach();
@@ -585,7 +601,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
+ scheduleAnimateResizePip(destinationBounds, mEnterAnimationDuration,
null /* updateBoundsCallback */);
}
@@ -935,6 +951,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|| direction == TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
// Animate with a cross-fade if enabled and seamless resize is disables by the app.
final boolean animateCrossFadeResize = mayAnimateFinishResize
+ && mPictureInPictureParams != null
&& !mPictureInPictureParams.isSeamlessResizeEnabled();
if (animateCrossFadeResize) {
// Take a snapshot of the PIP task and hide it. We'll show it and fade it out after
@@ -954,7 +971,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mMainExecutor.execute(() -> {
// Start animation to fade out the snapshot.
final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
- animator.setDuration(mEnterExitAnimationDuration);
+ animator.setDuration(mEnterAnimationDuration);
animator.addUpdateListener(animation -> {
final float alpha = (float) animation.getAnimatedValue();
final SurfaceControl.Transaction transaction =
@@ -999,10 +1016,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.scheduleFinishEnterPip(mToken, destinationBounds);
} else if (isOutPipDirection(direction)) {
- // If we are animating to fullscreen, then we need to reset the override bounds
- // on the task to ensure that the task "matches" the parent's bounds.
- taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
- ? null : destinationBounds;
+ // If we are animating to fullscreen or split screen, then we need to reset the
+ // override bounds on the task to ensure that the task "matches" the parent's bounds.
+ taskBounds = null;
applyWindowingModeChangeOnExit(wct, direction);
} else {
// Just a resize in PIP
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 580861cf4974..65f3d3a92476 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -244,9 +244,11 @@ public class PhonePipMenuController implements PipMenuController {
*/
public void showMenuWithPossibleDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean willResizeMenu, boolean showResizeHandle) {
- // hide all visible controls including close button and etc. first, this is to ensure
- // menu is totally invisible during the transition to eliminate unpleasant artifacts
- fadeOutMenu();
+ if (willResizeMenu) {
+ // hide all visible controls including close button and etc. first, this is to ensure
+ // menu is totally invisible during the transition to eliminate unpleasant artifacts
+ fadeOutMenu();
+ }
showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
willResizeMenu /* withDelay=willResizeMenu here */, showResizeHandle);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 9a584c67f97c..d75c1d65614d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import android.app.ActivityManager;
@@ -33,6 +34,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -44,6 +46,7 @@ import android.view.DisplayInfo;
import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -51,9 +54,14 @@ import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -66,12 +74,14 @@ import com.android.wm.shell.pip.PipUtils;
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
-public class PipController implements PipTransitionController.PipTransitionCallback {
+public class PipController implements PipTransitionController.PipTransitionCallback,
+ RemoteCallable<PipController> {
private static final String TAG = "PipController";
private Context mContext;
@@ -85,12 +95,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipBoundsState mPipBoundsState;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
- protected final PipImpl mImpl = new PipImpl();
+ private Optional<OneHandedController> mOneHandedController;
+ protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
private boolean mIsInFixedRotation;
- private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
+ private IPipAnimationListener mPinnedStackAnimationRecentsCallback;
protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
@@ -230,7 +241,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
- TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
+ TaskStackListenerImpl taskStackListener,
+ Optional<OneHandedController> oneHandedController,
+ ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
Slog.w(TAG, "Device doesn't support Pip feature");
return null;
@@ -239,7 +252,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, mainExecutor)
+ taskStackListener, oneHandedController, mainExecutor)
.mImpl;
}
@@ -255,6 +268,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
+ Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
// Ensure that we are the primary user's SystemUI.
@@ -264,6 +278,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
mContext = context;
+ mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
@@ -274,6 +289,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener;
+ mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mainExecutor);
@@ -364,6 +380,31 @@ public class PipController implements PipTransitionController.PipTransitionCallb
clearedTask /* skipAnimation */);
}
});
+
+ mOneHandedController.ifPresent(controller -> {
+ controller.asOneHanded().registerTransitionCallback(
+ new OneHandedTransitionCallback() {
+ @Override
+ public void onStartFinished(Rect bounds) {
+ mTouchHandler.setOhmOffset(bounds.top);
+ }
+
+ @Override
+ public void onStopFinished(Rect bounds) {
+ mTouchHandler.setOhmOffset(bounds.top);
+ }
+ });
+ });
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
}
private void onConfigurationChanged(Configuration newConfig) {
@@ -474,7 +515,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.setOneShotAnimationType(animationType);
}
- private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+ private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
}
@@ -494,15 +535,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
}
- /**
- * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
- * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
- */
- private void setPipExclusionBoundsChangeListener(
- Consumer<Rect> pipExclusionBoundsChangeListener) {
- mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
- }
-
@Override
public void onPipTransitionStarted(int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) {
@@ -512,7 +544,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Disable touches while the animation is running
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
- mPinnedStackAnimationRecentsCallback.accept(true);
+ try {
+ mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e);
+ }
}
}
@@ -638,7 +674,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipInputConsumer.dump(pw, innerPrefix);
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
private class PipImpl implements Pip {
+ private IPipImpl mIPip;
+
+ @Override
+ public IPip createExternalInterface() {
+ if (mIPip != null) {
+ mIPip.invalidate();
+ }
+ mIPip = new IPipImpl(PipController.this);
+ return mIPip;
+ }
+
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
mMainExecutor.execute(() -> {
@@ -696,13 +746,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
- mMainExecutor.execute(() -> {
- PipController.this.setPinnedStackAnimationListener(callback);
- });
- }
-
- @Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationType(animationType);
@@ -712,7 +755,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
mMainExecutor.execute(() -> {
- PipController.this.setPipExclusionBoundsChangeListener(listener);
+ mPipBoundsState.setPipExclusionBoundsChangeCallback(listener);
});
}
@@ -724,37 +767,99 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- int shelfHeight) {
- Rect[] result = new Rect[1];
+ public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
- result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, shelfHeight);
+ PipController.this.dump(pw);
});
} catch (InterruptedException e) {
- Slog.e(TAG, "Failed to start swipe pip to home");
+ Slog.e(TAG, "Failed to dump PipController in 2s");
}
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub {
+ private PipController mController;
+ private IPipAnimationListener mListener;
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final PipController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.setPinnedStackAnimationListener(null);
+ });
+ }
+ };
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ int shelfHeight) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.startSwipePipToHome(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, shelfHeight);
+ }, true /* blocking */);
return result[0];
}
@Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- mMainExecutor.execute(() -> {
- PipController.this.stopSwipePipToHome(componentName, destinationBounds);
- });
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> {
+ controller.stopSwipePipToHome(componentName, destinationBounds);
+ });
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- PipController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump PipController in 2s");
- }
+ public void setShelfHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
+ (controller) -> {
+ controller.setShelfHeight(visible, height);
+ });
+ }
+
+ @Override
+ public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+ (controller) -> {
+ if (mListener != null) {
+ // Reset the old death recipient
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ // Register the death recipient for the new listener to clear the listener
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.setPinnedStackAnimationListener(listener);
+ });
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 9ee6a221c80c..c26b686f91fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -16,13 +16,14 @@
package com.android.wm.shell.pip.phone;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.TransitionDrawable;
-import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -41,8 +42,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipUiEventLogger;
-import java.util.concurrent.TimeUnit;
-
import kotlin.Unit;
/**
@@ -89,7 +88,9 @@ public class PipDismissTargetHandler {
// Allow dragging the PIP to a location to close it
private boolean mEnableDismissDragToEdge;
+ private int mTargetSize;
private int mDismissAreaHeight;
+ private float mMagneticFieldRadiusPercent = 1f;
private final Context mContext;
private final PipMotionHelper mMotionHelper;
@@ -183,18 +184,27 @@ public class PipDismissTargetHandler {
}
final Resources res = mContext.getResources();
- final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
final FrameLayout.LayoutParams newParams =
- new FrameLayout.LayoutParams(targetSize, targetSize);
+ new FrameLayout.LayoutParams(mTargetSize, mTargetSize);
newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.floating_dismiss_bottom_margin);
mTargetView.setLayoutParams(newParams);
// Set the magnetic field radius equal to the target size from the center of the target
- mMagneticTarget.setMagneticFieldRadiusPx(
- (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
+ }
+
+ /**
+ * Increase or decrease the field radius of the magnet object, e.g. with larger percent,
+ * PiP will magnetize to the field sooner.
+ */
+ public void setMagneticFieldRadiusPercent(float percent) {
+ mMagneticFieldRadiusPercent = percent;
+ mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize
+ * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
}
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
@@ -234,6 +244,7 @@ public class PipDismissTargetHandler {
lp.setTitle("pip-dismiss-overlay");
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.setFitInsetsTypes(0 /* types */);
return lp;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 8ab405bca7db..1bfae53853c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -76,7 +76,6 @@ public class PipMenuView extends FrameLayout {
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
private static final long MENU_FADE_DURATION = 125;
- private static final long MENU_SLOW_FADE_DURATION = 175;
private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
private static final float MENU_BACKGROUND_ALPHA = 0.3f;
@@ -253,9 +252,7 @@ public class PipMenuView extends FrameLayout {
mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
- ? MENU_FADE_DURATION
- : MENU_SLOW_FADE_DURATION);
+ mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
if (allowMenuTimeout) {
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 81a7ae1be482..402846f79ab7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -29,7 +29,6 @@ import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;
-import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.AnimationHandler;
import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
import androidx.dynamicanimation.animation.SpringForce;
@@ -489,8 +488,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/**
* Animates the PiP to offset it from the IME or shelf.
*/
- @VisibleForTesting
- public void animateToOffset(Rect originalBounds, int offset) {
+ void animateToOffset(Rect originalBounds, int offset) {
if (DEBUG) {
Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
+ " callers=\n" + Debug.getCallers(5, " "));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
index 805123f81d81..f8125fd5764e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
@@ -16,111 +16,115 @@
package com.android.wm.shell.pip.phone;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
/**
* Helper class to calculate the new size given two-fingers pinch to resize.
*/
public class PipPinchResizingAlgorithm {
- private static final Rect TMP_RECT = new Rect();
+
+ private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45;
+ private static final float OVERROTATE_DAMP_FACTOR = 0.4f;
+ private static final float ANGLE_THRESHOLD = 5f;
+
+ private final PointF mTmpDownVector = new PointF();
+ private final PointF mTmpLastVector = new PointF();
+ private final PointF mTmpDownCentroid = new PointF();
+ private final PointF mTmpLastCentroid = new PointF();
+
/**
- * Given inputs and requirements and current PiP bounds, return the new size.
- *
- * @param x0 x-coordinate of the primary input.
- * @param y0 y-coordinate of the primary input.
- * @param x1 x-coordinate of the secondary input.
- * @param y1 y-coordinate of the secondary input.
- * @param downx0 x-coordinate of the original down point of the primary input.
- * @param downy0 y-coordinate of the original down ponit of the primary input.
- * @param downx1 x-coordinate of the original down point of the secondary input.
- * @param downy1 y-coordinate of the original down point of the secondary input.
- * @param currentPipBounds current PiP bounds.
- * @param minVisibleWidth minimum visible width.
- * @param minVisibleHeight minimum visible height.
- * @param maxSize max size.
- * @return The new resized PiP bounds, sharing the same center.
+ * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in
+ * degrees that the PIP should be rotated.
*/
- public static Rect pinchResize(float x0, float y0, float x1, float y1,
- float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds,
- int minVisibleWidth, int minVisibleHeight, Point maxSize) {
-
- int width = currentPipBounds.width();
- int height = currentPipBounds.height();
- int left = currentPipBounds.left;
- int top = currentPipBounds.top;
- int right = currentPipBounds.right;
- int bottom = currentPipBounds.bottom;
- final float aspect = (float) width / (float) height;
- final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1));
- final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1));
- final int dx = (int) ((x0 - downx0 + x1 - downx1) / 2);
- final int dy = (int) ((y0 - downy0 + y1 - downy1) / 2);
-
- width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x));
- height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y));
-
- // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
- // drag axis. What ever is producing the bigger rectangle will be chosen.
- int width1;
- int width2;
- int height1;
- int height2;
- if (aspect > 1.0f) {
- // Assuming that the width is our target we calculate the height.
- width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width));
- height1 = Math.round((float) width1 / aspect);
- if (height1 < minVisibleHeight) {
- // If the resulting height is too small we adjust to the minimal size.
- height1 = minVisibleHeight;
- width1 = Math.max(minVisibleWidth,
- Math.min(maxSize.x, Math.round((float) height1 * aspect)));
- }
- // Assuming that the height is our target we calculate the width.
- height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height));
- width2 = Math.round((float) height2 * aspect);
- if (width2 < minVisibleWidth) {
- // If the resulting width is too small we adjust to the minimal size.
- width2 = minVisibleWidth;
- height2 = Math.max(minVisibleHeight,
- Math.min(maxSize.y, Math.round((float) width2 / aspect)));
- }
- } else {
- // Assuming that the width is our target we calculate the height.
- width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width));
- height1 = Math.round((float) width1 / aspect);
- if (height1 < minVisibleHeight) {
- // If the resulting height is too small we adjust to the minimal size.
- height1 = minVisibleHeight;
- width1 = Math.max(minVisibleWidth,
- Math.min(maxSize.x, Math.round((float) height1 * aspect)));
- }
- // Assuming that the height is our target we calculate the width.
- height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height));
- width2 = Math.round((float) height2 * aspect);
- if (width2 < minVisibleWidth) {
- // If the resulting width is too small we adjust to the minimal size.
- width2 = minVisibleWidth;
- height2 = Math.max(minVisibleHeight,
- Math.min(maxSize.y, Math.round((float) width2 / aspect)));
- }
- }
+ public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint,
+ PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize,
+ Rect initialBounds, Rect resizeBoundsOut) {
+ float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x,
+ downSecondPoint.y - downPoint.y);
+ float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x,
+ lastSecondPoint.y - lastPoint.y);
+ float minScale = getMinScale(initialBounds, minSize);
+ float maxScale = getMaxScale(initialBounds, maxSize);
+ float scale = Math.max(minScale, Math.min(maxScale, dist / downDist));
+
+ // Scale the bounds by the change in distance between the points
+ resizeBoundsOut.set(initialBounds);
+ scaleRectAboutCenter(resizeBoundsOut, scale);
+
+ // Translate by the centroid movement
+ getCentroid(downPoint, downSecondPoint, mTmpDownCentroid);
+ getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid);
+ resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x),
+ (int) (mTmpLastCentroid.y - mTmpDownCentroid.y));
+
+ // Calculate the angle
+ mTmpDownVector.set(downSecondPoint.x - downPoint.x,
+ downSecondPoint.y - downPoint.y);
+ mTmpLastVector.set(lastSecondPoint.x - lastPoint.x,
+ lastSecondPoint.y - lastPoint.y);
+ float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector),
+ dot(mTmpDownVector, mTmpLastVector));
+ return constrainRotationAngle((float) Math.toDegrees(angle));
+ }
+
+ private float getMinScale(Rect bounds, Point minSize) {
+ return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height());
+ }
+
+ private float getMaxScale(Rect bounds, Point maxSize) {
+ return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height());
+ }
+
+ private float constrainRotationAngle(float angle) {
+ // Remove some degrees so that user doesn't immediately start rotating until a threshold
+ return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD));
+ }
+
+ /**
+ * Given the current rotation angle, dampen it so that as it approaches the maximum angle,
+ * dampen it.
+ */
+ private float dampedRotate(float amount) {
+ if (Float.compare(amount, 0) == 0) return 0;
- // Use the bigger of the two rectangles if the major change was positive, otherwise
- // do the opposite.
- final boolean grows = width > (right - left) || height > (bottom - top);
- if (grows == (width1 * height1 > width2 * height2)) {
- width = width1;
- height = height1;
- } else {
- width = width2;
- height = height2;
+ float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION;
+ f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
}
+ return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION;
+ }
+
+ /**
+ * Returns a value that corresponds to y = (f - 1)^3 + 1.
+ */
+ private float overRotateInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
- TMP_RECT.set(currentPipBounds.centerX() - width / 2,
- currentPipBounds.centerY() - height / 2,
- currentPipBounds.centerX() + width / 2,
- currentPipBounds.centerY() + height / 2);
- TMP_RECT.offset(dx, dy);
- return TMP_RECT;
+ private void getCentroid(PointF p1, PointF p2, PointF centroidOut) {
+ centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2);
+ }
+
+ private float dot(PointF p1, PointF p2) {
+ return p1.x * p2.x + p1.y * p2.y;
+ }
+
+ private float cross(PointF p1, PointF p2) {
+ return p1.x * p2.y - p1.y * p2.x;
+ }
+
+ private void scaleRectAboutCenter(Rect r, float scale) {
+ if (scale != 1.0f) {
+ int cx = r.centerX();
+ int cy = r.centerY();
+ r.offset(-cx, -cy);
+ r.scale(scale);
+ r.offset(cx, cy);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index c726c012c6a0..588571f7171e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -63,10 +63,7 @@ public class PipResizeGestureHandler {
private static final String TAG = "PipResizeGestureHandler";
private static final int PINCH_RESIZE_SNAP_DURATION = 250;
- private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45;
private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
- private static final float OVERROTATE_DAMP_FACTOR = 0.4f;
- private static final float ANGLE_THRESHOLD = 5f;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -74,18 +71,22 @@ public class PipResizeGestureHandler {
private final PipBoundsState mPipBoundsState;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PhonePipMenuController mPhonePipMenuController;
+ private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipUiEventLogger mPipUiEventLogger;
+ private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
private final int mDisplayId;
private final ShellExecutor mMainExecutor;
private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
- private final PointF mDownSecondaryPoint = new PointF();
+ private final PointF mDownSecondPoint = new PointF();
+ private final PointF mLastPoint = new PointF();
+ private final PointF mLastSecondPoint = new PointF();
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final Rect mLastResizeBounds = new Rect();
private final Rect mUserResizeBounds = new Rect();
- private final Rect mLastDownBounds = new Rect();
+ private final Rect mDownBounds = new Rect();
private final Rect mDragCornerSize = new Rect();
private final Rect mTmpTopLeftCorner = new Rect();
private final Rect mTmpTopRightCorner = new Rect();
@@ -103,12 +104,8 @@ public class PipResizeGestureHandler {
private boolean mIsEnabled;
private boolean mEnablePinchResize;
private boolean mIsSysUiStateValid;
- // For drag-resize
private boolean mThresholdCrossed;
- // For pinch-resize
- private boolean mThresholdCrossed0;
- private boolean mThresholdCrossed1;
- private boolean mUsingPinchToZoom = true;
+ private boolean mOngoingPinchToResize = false;
private float mAngle = 0;
int mFirstIndex = -1;
int mSecondIndex = -1;
@@ -117,12 +114,14 @@ public class PipResizeGestureHandler {
private InputEventReceiver mInputEventReceiver;
private int mCtrlType;
+ private int mOhmOffset;
public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
- PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
- Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
- PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) {
+ PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler,
+ Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+ PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
+ ShellExecutor mainExecutor) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = mainExecutor;
@@ -130,10 +129,12 @@ public class PipResizeGestureHandler {
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
+ mPipDismissTargetHandler = pipDismissTargetHandler;
mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
+ mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
}
public void init() {
@@ -236,7 +237,7 @@ public class PipResizeGestureHandler {
}
if (ev instanceof MotionEvent) {
- if (mUsingPinchToZoom) {
+ if (mOngoingPinchToResize) {
onPinchResize((MotionEvent) ev);
} else {
onDragCornerResize((MotionEvent) ev);
@@ -248,7 +249,7 @@ public class PipResizeGestureHandler {
* Checks if there is currently an on-going gesture, either drag-resize or pinch-resize.
*/
public boolean hasOngoingGesture() {
- return mCtrlType != CTRL_NONE || mUsingPinchToZoom;
+ return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
}
/**
@@ -293,6 +294,10 @@ public class PipResizeGestureHandler {
return mEnablePinchResize;
}
+ public boolean isResizing() {
+ return mAllowGesture;
+ }
+
public boolean willStartResizeGesture(MotionEvent ev) {
if (isInValidSysUiState()) {
switch (ev.getActionMasked()) {
@@ -305,7 +310,7 @@ public class PipResizeGestureHandler {
case MotionEvent.ACTION_POINTER_DOWN:
if (mEnablePinchResize && ev.getPointerCount() == 2) {
onPinchResize(ev);
- mUsingPinchToZoom = true;
+ mOngoingPinchToResize = true;
return true;
}
break;
@@ -361,6 +366,7 @@ public class PipResizeGestureHandler {
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mFirstIndex = -1;
mSecondIndex = -1;
+ mAllowGesture = false;
finishResize();
}
@@ -370,14 +376,16 @@ public class PipResizeGestureHandler {
if (action == MotionEvent.ACTION_POINTER_DOWN) {
if (mFirstIndex == -1 && mSecondIndex == -1) {
+ mAllowGesture = true;
mFirstIndex = 0;
mSecondIndex = 1;
mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
- mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
-
+ mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+ mDownBounds.set(mPipBoundsState.getBounds());
- mLastDownBounds.set(mPipBoundsState.getBounds());
- mLastResizeBounds.set(mLastDownBounds);
+ mLastPoint.set(mDownPoint);
+ mLastSecondPoint.set(mLastSecondPoint);
+ mLastResizeBounds.set(mDownBounds);
}
}
@@ -390,137 +398,40 @@ public class PipResizeGestureHandler {
float y0 = ev.getRawY(mFirstIndex);
float x1 = ev.getRawX(mSecondIndex);
float y1 = ev.getRawY(mSecondIndex);
+ mLastPoint.set(x0, y0);
+ mLastSecondPoint.set(x1, y1);
- double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y);
- double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y);
// Capture inputs
- if (hypot0 > mTouchSlop && !mThresholdCrossed0) {
- mInputMonitor.pilferPointers();
- mThresholdCrossed0 = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(x0, y0);
- }
- if (hypot1 > mTouchSlop && !mThresholdCrossed1) {
+ if (!mThresholdCrossed
+ && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop
+ || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) {
mInputMonitor.pilferPointers();
- mThresholdCrossed1 = true;
+ mThresholdCrossed = true;
// Reset the down to begin resizing from this point
- mDownSecondaryPoint.set(x1, y1);
+ mDownPoint.set(mLastPoint);
+ mDownSecondPoint.set(mLastSecondPoint);
}
- if (mThresholdCrossed0 || mThresholdCrossed1) {
+
+ if (mThresholdCrossed) {
if (mPhonePipMenuController.isMenuVisible()) {
mPhonePipMenuController.hideMenu();
}
- x0 = mThresholdCrossed0 ? x0 : mDownPoint.x;
- y0 = mThresholdCrossed0 ? y0 : mDownPoint.y;
- x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x;
- y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y;
-
- final Rect originalPipBounds = mPipBoundsState.getBounds();
- int focusX = (int) originalPipBounds.centerX();
- int focusY = (int) originalPipBounds.centerY();
-
- float down0X = mDownPoint.x;
- float down0Y = mDownPoint.y;
- float down1X = mDownSecondaryPoint.x;
- float down1Y = mDownSecondaryPoint.y;
-
- float angle = 0;
- if (down0X > focusX && down0Y < focusY && down1X < focusX && down1Y > focusY) {
- // Top right + Bottom left pinch to zoom.
- angle = calculateRotationAngle(mLastResizeBounds.centerX(),
- mLastResizeBounds.centerY(), x0, y0, x1, y1, true);
- } else if (down1X > focusX && down1Y < focusY
- && down0X < focusX && down0Y > focusY) {
- // Top right + Bottom left pinch to zoom.
- angle = calculateRotationAngle(mLastResizeBounds.centerX(),
- mLastResizeBounds.centerY(), x1, y1, x0, y0, true);
- } else if (down0X < focusX && down0Y < focusY
- && down1X > focusX && down1Y > focusY) {
- // Top left + bottom right pinch to zoom.
- angle = calculateRotationAngle(mLastResizeBounds.centerX(),
- mLastResizeBounds.centerY(), x0, y0, x1, y1, false);
- } else if (down1X < focusX && down1Y < focusY
- && down0X > focusX && down0Y > focusY) {
- // Top left + bottom right pinch to zoom.
- angle = calculateRotationAngle(mLastResizeBounds.centerX(),
- mLastResizeBounds.centerY(), x1, y1, x0, y0, false);
- }
- mAngle = angle;
+ mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
+ mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
+ mDownBounds, mLastResizeBounds);
- mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1,
- mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y,
- originalPipBounds, mMinSize.x, mMinSize.y, mMaxSize));
-
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
- (float) -mAngle, null);
+ mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
+ mAngle, null);
mPipBoundsState.setHasUserResizedPip(true);
}
}
}
- private float calculateRotationAngle(int pivotX, int pivotY, float topX, float topY,
- float bottomX, float bottomY, boolean positive) {
-
- // The base angle is the angle formed by taking the angle between the center horizontal
- // and one of the corners.
- double baseAngle = Math.toDegrees(Math.atan2(Math.abs(mLastResizeBounds.top - pivotY),
- Math.abs(mLastResizeBounds.right - pivotX)));
-
- double angle0 = mThresholdCrossed0
- ? Math.toDegrees(Math.atan2(pivotY - topY, topX - pivotX)) : baseAngle;
- double angle1 = mThresholdCrossed0
- ? Math.toDegrees(Math.atan2(pivotY - bottomY, bottomX - pivotX)) : baseAngle;
-
-
- if (positive) {
- angle1 = angle1 < 0 ? 180 + angle1 : angle1 - 180;
- } else {
- angle0 = angle0 < 0 ? -angle0 - 180 : 180 - angle0;
- angle1 = -angle1;
- }
-
- // Calculate the percentage difference of [0, 90] compare to the base angle.
- double diff0 = (Math.max(-90, Math.min(angle0, 90)) - baseAngle) / 90;
- double diff1 = (Math.max(-90, Math.min(angle1, 90)) - baseAngle) / 90;
-
- final float angle =
- (float) (diff0 + diff1) / 2 * PINCH_RESIZE_MAX_ANGLE_ROTATION * (positive ? 1 : -1);
-
- // Remove some degrees so that user doesn't immediately start rotating until a threshold
- return angle / Math.abs(angle)
- * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD));
- }
-
- /**
- * Given the current rotation angle, dampen it so that as it approaches the maximum angle,
- * dampen it.
- */
- private float dampedRotate(float amount) {
- if (Float.compare(amount, 0) == 0) return 0;
-
- float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION;
- f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f)));
-
- // Clamp this factor, f, to -1 < f < 1
- if (Math.abs(f) >= 1) {
- f /= Math.abs(f);
- }
- return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION;
- }
-
- /**
- * Returns a value that corresponds to y = (f - 1)^3 + 1.
- */
- private float overRotateInfluenceCurve(float f) {
- f -= 1.0f;
- return f * f * f + 1.0f;
- }
-
private void onDragCornerResize(MotionEvent ev) {
int action = ev.getActionMasked();
float x = ev.getX();
- float y = ev.getY();
+ float y = ev.getY() - mOhmOffset;
if (action == MotionEvent.ACTION_DOWN) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.setEmpty();
@@ -528,9 +439,9 @@ public class PipResizeGestureHandler {
if (mAllowGesture) {
setCtrlType((int) x, (int) y);
mDownPoint.set(x, y);
- mLastDownBounds.set(mPipBoundsState.getBounds());
+ mDownBounds.set(mPipBoundsState.getBounds());
}
- if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
+ if (!currentPipBounds.contains((int) x, (int) y)
&& mPhonePipMenuController.isMenuVisible()) {
mPhonePipMenuController.hideMenu();
}
@@ -559,11 +470,11 @@ public class PipResizeGestureHandler {
mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
mMinSize.y, mMaxSize, true,
- mLastDownBounds.width() > mLastDownBounds.height()));
+ mDownBounds.width() > mDownBounds.height()));
mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
true /* useCurrentSize */);
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
+ mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
null);
mPipBoundsState.setHasUserResizedPip(true);
}
@@ -587,21 +498,23 @@ public class PipResizeGestureHandler {
// Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
// position correctly. Drag-resize does not need to move, so just finalize resize.
- if (mUsingPinchToZoom) {
+ if (mOngoingPinchToResize) {
final Rect startBounds = new Rect(mLastResizeBounds);
// If user resize is pretty close to max size, just auto resize to max.
if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
- mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
+ resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
}
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, callback);
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
}
+ mPipDismissTargetHandler
+ .setMagneticFieldRadiusPercent((float) mLastResizeBounds.width() / mMinSize.x);
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
} else {
@@ -612,7 +525,7 @@ public class PipResizeGestureHandler {
private void resetState() {
mCtrlType = CTRL_NONE;
mAngle = 0;
- mUsingPinchToZoom = false;
+ mOngoingPinchToResize = false;
mAllowGesture = false;
mThresholdCrossed = false;
}
@@ -637,6 +550,24 @@ public class PipResizeGestureHandler {
mMinSize.set(minX, minY);
}
+ void setOhmOffset(int offset) {
+ mOhmOffset = offset;
+ }
+
+ private float distanceBetween(PointF p1, PointF p2) {
+ return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
+ }
+
+ private void resizeRectAboutCenter(Rect rect, int w, int h) {
+ int cx = rect.centerX();
+ int cy = rect.centerY();
+ int l = cx - w / 2;
+ int r = l + w;
+ int t = cy - h / 2;
+ int b = t + h;
+ rect.set(l, t, r, b);
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
@@ -645,6 +576,7 @@ public class PipResizeGestureHandler {
pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
+ pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
}
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 543ecfcf1a33..d474b6638e4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,8 +57,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.function.Consumer;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
@@ -80,7 +78,6 @@ public class PipTouchHandler {
private final ShellExecutor mMainExecutor;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener;
private final PhonePipMenuController mMenuController;
private final AccessibilityManager mAccessibilityManager;
@@ -179,13 +176,13 @@ public class PipTouchHandler {
mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController,
floatingContentCoordinator);
- mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
- mMotionHelper, pipTaskOrganizer, this::getMovementBounds,
- this::updateMovementBounds, pipUiEventLogger, menuController,
- mainExecutor);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mMotionHelper, pipTaskOrganizer, mPipDismissTargetHandler,
+ this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
if (mPipBoundsState.isStashed()) {
@@ -288,11 +285,6 @@ public class PipTouchHandler {
mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
}
- // Reset exclusion to none.
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(new Rect());
- }
mPipResizeGestureHandler.onActivityUnpinned();
}
@@ -672,7 +664,7 @@ public class PipTouchHandler {
} else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
// Try and restore the PiP to the closest edge, using the saved snap fraction
// if possible
- if (resize) {
+ if (resize && !mPipResizeGestureHandler.isResizing()) {
if (mDeferResizeToNormalBoundsUntilRotation == -1) {
// This is a very special case: when the menu is expanded and visible,
// navigating to another activity can trigger auto-enter PiP, and if the
@@ -790,7 +782,6 @@ public class PipTouchHandler {
private final Point mStartPosition = new Point();
private final PointF mDelta = new PointF();
private boolean mShouldHideMenuAfterFling;
- private float mDownSavedFraction = -1f;
@Override
public void onDown(PipTouchState touchState) {
@@ -804,7 +795,6 @@ public class PipTouchHandler {
mMovementWithinDismiss = touchState.getDownTouchPosition().y
>= mPipBoundsState.getMovementBounds().bottom;
mMotionHelper.setSpringingToTouch(false);
- mDownSavedFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
// If the menu is still visible then just poke the menu
// so that it will timeout after the user stops touching it
@@ -875,9 +865,12 @@ public class PipTouchHandler {
if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
} else {
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ if (mPipBoundsState.isStashed()) {
+ // Reset stashed state if previously stashed
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ }
mMotionHelper.flingToSnapTarget(vel.x, vel.y,
this::flingEndAction /* endAction */);
}
@@ -904,19 +897,19 @@ public class PipTouchHandler {
mMotionHelper.expandLeavePip();
}
} else if (mMenuState != MENU_STATE_FULL) {
- if (!mTouchState.isWaitingForDoubleTap()) {
- if (mPipBoundsState.isStashed()) {
- animateToUnStashedState();
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
- mPipBoundsState.setStashed(STASH_TYPE_NONE);
- } else {
- // User has stalled long enough for this not to be a drag or a double tap,
- // just expand the menu
- mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
- }
+ if (mPipBoundsState.isStashed()) {
+ // Unstash immediately if stashed, and don't wait for the double tap timeout
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ mTouchState.removeDoubleTapTimeoutCallback();
+ } else if (!mTouchState.isWaitingForDoubleTap()) {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
} else {
// Next touch event _may_ be the second tap for the double-tap, schedule a
// fallback runnable to trigger the menu if no touch event occurs before the
@@ -924,15 +917,10 @@ public class PipTouchHandler {
mTouchState.scheduleDoubleTapTimeoutCallback();
}
}
- mDownSavedFraction = -1f;
return true;
}
private void stashEndAction() {
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds());
- }
if (mPipBoundsState.getBounds().left < 0
&& mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
mPipUiEventLogger.log(
@@ -944,6 +932,7 @@ public class PipTouchHandler {
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
}
+ mMenuController.hideMenu();
}
private void flingEndAction() {
@@ -952,11 +941,6 @@ public class PipTouchHandler {
// dismiss overlay, so just finish it after the animation completes
mMenuController.hideMenu();
}
- // Reset exclusion to none.
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(new Rect());
- }
}
private boolean shouldStash(PointF vel, Rect motionBounds) {
@@ -980,11 +964,6 @@ public class PipTouchHandler {
}
}
- void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
- mPipExclusionBoundsChangeListener = new WeakReference<>(pipExclusionBoundsChangeListener);
- pipExclusionBoundsChangeListener.accept(mPipBoundsState.getBounds());
- }
-
/**
* Updates the current movement bounds based on whether the menu is currently visible and
* resized.
@@ -1035,6 +1014,10 @@ public class PipTouchHandler {
: mPipBoundsState.getBounds();
}
+ void setOhmOffset(int offset) {
+ mPipResizeGestureHandler.setOhmOffset(offset);
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 2b0a0cd3de20..963a3dc70262 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -28,7 +28,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
// with those in the framework ProtoLogGroup
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
new file mode 100644
index 000000000000..0c46eaba18ae
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -0,0 +1,77 @@
+/*
+ * 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.splitscreen;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages.
+ */
+ oneway void exitSplitScreen() = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
new file mode 100644
index 000000000000..faab4c2009cf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.splitscreen;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index e7cd38fb4bca..01a81d2ff5e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -40,15 +40,17 @@ class SideStage extends StageTaskListener {
void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
WindowContainerTransaction wct) {
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setHidden(rootToken, false)
- .setBounds(rootToken, rootBounds)
+ wct.setBounds(rootToken, rootBounds)
.reparent(task.token, rootToken, true /* onTop*/)
// Moving the root task to top after the child tasks were repareted , or the root
// task cannot be visible and focused.
- .reorder(rootToken, true);
+ .reorder(rootToken, true /* onTop */);
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
@@ -62,10 +64,7 @@ class SideStage extends StageTaskListener {
boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
if (task == null) return false;
-
- wct.setHidden(mRootTaskInfo.token, true)
- .reorder(mRootTaskInfo.token, false)
- .reparent(task.token, newParent, false /* onTop */);
+ wct.reparent(task.token, newParent, false /* onTop */);
return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 25a84bd46484..340b55d7f446 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -35,7 +35,7 @@ import com.android.wm.shell.draganddrop.DragAndDropPolicy;
* TODO: Figure out which of these are actually needed outside of the Shell
*/
@ExternalThread
-public interface SplitScreen extends DragAndDropPolicy.Starter {
+public interface SplitScreen {
/**
* Stage position isn't specified normally meaning to use what ever it is currently set to.
*/
@@ -89,35 +89,10 @@ public interface SplitScreen extends DragAndDropPolicy.Starter {
void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
}
- /** @return {@code true} if split-screen is currently visible. */
- boolean isSplitScreenVisible();
- /** Moves a task in the side-stage of split-screen. */
- boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
- /** Moves a task in the side-stage of split-screen. */
- boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @StagePosition int sideStagePosition);
- /** Removes a task from the side-stage of split-screen. */
- boolean removeFromSideStage(int taskId);
- /** Sets the position of the side-stage. */
- void setSideStagePosition(@StagePosition int sideStagePosition);
- /** Hides the side-stage if it is currently visible. */
- void setSideStageVisibility(boolean visible);
-
- /** Removes the split-screen stages. */
- void exitSplitScreen();
- /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
- void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
- /** Gets the stage bounds. */
- void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
-
- void registerSplitScreenListener(SplitScreenListener listener);
- void unregisterSplitScreenListener(SplitScreenListener listener);
-
- void startTask(int taskId,
- @StageType int stage, @StagePosition int position, @Nullable Bundle options);
- void startShortcut(String packageName, String shortcutId, @StageType int stage,
- @StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, Context context,
- @Nullable Intent fillInIntent, @StageType int stage,
- @StagePosition int position, @Nullable Bundle options);
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index bb6f6f259a1e..d4362efe462d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
@@ -34,19 +35,24 @@ import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
+import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
import java.io.PrintWriter;
@@ -55,7 +61,8 @@ import java.io.PrintWriter;
* {@link SplitScreen}.
* @see StageCoordinator
*/
-public class SplitScreenController implements DragAndDropPolicy.Starter {
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
private static final String TAG = SplitScreenController.class.getSimpleName();
private final ShellTaskOrganizer mTaskOrganizer;
@@ -84,6 +91,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter {
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
public void onOrganizerRegistered() {
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -172,13 +189,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter {
}
}
- public void startIntent(PendingIntent intent, Context context,
- Intent fillInIntent, @SplitScreen.StageType int stage,
- @SplitScreen.StagePosition int position, @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
+ @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
- intent.send(context, 0, fillInIntent, null, null, null, options);
+ intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
@@ -242,121 +259,170 @@ public class SplitScreenController implements DragAndDropPolicy.Starter {
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
private class SplitScreenImpl implements SplitScreen {
- @Override
- public boolean isSplitScreenVisible() {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.isSplitScreenVisible();
- }, Boolean.class);
- }
+ private ISplitScreenImpl mISplitScreen;
@Override
- public boolean moveToSideStage(int taskId, int sideStagePosition) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.moveToSideStage(taskId, sideStagePosition);
- }, Boolean.class);
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
}
+ }
- @Override
- public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- int sideStagePosition) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.moveToSideStage(task, sideStagePosition);
- }, Boolean.class);
- }
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
- @Override
- public boolean removeFromSideStage(int taskId) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.removeFromSideStage(taskId);
- }, Boolean.class);
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
}
- @Override
- public void setSideStagePosition(int sideStagePosition) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.setSideStagePosition(sideStagePosition);
- });
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
}
@Override
- public void setSideStageVisibility(boolean visible) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.setSideStageVisibility(visible);
- });
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
}
@Override
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.enterSplitScreen(taskId, leftOrTop);
- });
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
}
@Override
public void exitSplitScreen() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.exitSplitScreen();
- });
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen();
+ });
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
}
@Override
- public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- try {
- mMainExecutor.executeBlocking(() -> {
- SplitScreenController.this.getStageBounds(outTopOrLeftBounds,
- outBottomOrRightBounds);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get stage bounds in 2s");
- }
- }
-
- @Override
- public void registerSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.registerSplitScreenListener(listener);
- });
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
}
@Override
- public void unregisterSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.unregisterSplitScreenListener(listener);
- });
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
}
@Override
public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startTask(taskId, stage, position, options);
- });
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
}
@Override
public void startShortcut(String packageName, String shortcutId, int stage, int position,
@Nullable Bundle options, UserHandle user) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startShortcut(packageName, shortcutId, stage, position,
- options, user);
- });
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
}
@Override
- public void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
- int stage, int position, @Nullable Bundle options) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startIntent(intent, context, fillInIntent, stage,
- position, options);
- });
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
}
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
new file mode 100644
index 000000000000..546c406ef453
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.startingsurface;
+
+import com.android.wm.shell.startingsurface.IStartingWindowListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate starting windows.
+ */
+interface IStartingWindow {
+ /**
+ * Sets listener to get task launching callbacks.
+ */
+ oneway void setStartingWindowListener(IStartingWindowListener listener) = 43;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
new file mode 100644
index 000000000000..f562c8fc4f85
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.startingsurface;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get
+ * callbacks when need a new starting window.
+ */
+interface IStartingWindowListener {
+ /**
+ * Notifies when Shell going to create a new starting window.
+ * @param taskId The task Id
+ * @param supportedType The starting window type
+ */
+ oneway void onTaskLaunching(int taskId, int supportedType);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 3f9c2717731a..855faaa8e83e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.os.UserHandle.getUserHandleForUid;
+
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -30,6 +35,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
+import android.os.Trace;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.SplashScreenView;
@@ -38,6 +44,7 @@ import com.android.internal.R;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.TransactionPool;
import java.util.List;
@@ -58,6 +65,7 @@ public class SplashscreenContentDrawer {
// also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
private final Context mContext;
+ private final IconProvider mIconProvider;
private final int mMaxAnimatableIconDuration;
private int mIconSize;
@@ -69,10 +77,12 @@ public class SplashscreenContentDrawer {
private int mIconNormalExitDistance;
private int mIconEarlyExitDistance;
private final TransactionPool mTransactionPool;
+ private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
mContext = context;
+ mIconProvider = new IconProvider(context);
mMaxAnimatableIconDuration = maxAnimatableIconDuration;
mAppRevealDuration = appRevealAnimDuration;
mIconExitDuration = iconExitAnimDuration;
@@ -108,87 +118,102 @@ public class SplashscreenContentDrawer {
return new ColorDrawable(getSystemBGColor());
}
- SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
- int splashscreenContentResId) {
- updateDensity();
- // splash screen content will be deprecated after S.
- final SplashScreenView ssc =
- makeSplashscreenContentDrawable(context, splashscreenContentResId);
-
- if (ssc != null) {
- return ssc;
- }
-
- final SplashScreenWindowAttrs attrs =
- SplashScreenWindowAttrs.createWindowAttrs(context);
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
+ private @ColorInt int peekWindowBGColor(Context context) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
final Drawable themeBGDrawable;
-
- if (attrs.mWindowBgColor != 0) {
- themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
- } else if (attrs.mWindowBgResId != 0) {
- themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ if (mTmpAttrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
+ } else if (mTmpAttrs.mWindowBgResId != 0) {
+ themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
} else {
- Slog.w(TAG, "Window background not exist!");
themeBGDrawable = createDefaultBackgroundDrawable();
+ Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
}
+ final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return estimatedWindowBGColor;
+ }
+
+ private int estimateWindowBGColor(Drawable themeBGDrawable) {
+ final DrawableColorTester themeBGTester =
+ new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
+ if (themeBGTester.nonTransparentRatio() == 0) {
+ // the window background is transparent, unable to draw
+ Slog.w(TAG, "Window background is transparent, fill background with black color");
+ return getSystemBGColor();
+ } else {
+ return themeBGTester.getDominateColor();
+ }
+ }
+
+ SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) {
+ updateDensity();
+
+ getWindowAttrs(context, mTmpAttrs);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int animationDuration;
- final Drawable iconDrawable;
- if (attrs.mReplaceIcon != null) {
- iconDrawable = attrs.mReplaceIcon;
+ Drawable iconDrawable;
+ if (mTmpAttrs.mReplaceIcon != null) {
+ iconDrawable = mTmpAttrs.mReplaceIcon;
animationDuration = Math.max(0,
- Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
+ Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
} else {
- iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
- : context.getPackageManager().getDefaultActivityIcon();
+ iconDrawable = mIconProvider.getIconForUI(
+ ai, getUserHandleForUid(ai.applicationInfo.uid));
+ if (iconDrawable == null) {
+ iconDrawable = context.getPackageManager().getDefaultActivityIcon();
+ }
animationDuration = 0;
}
+ final int themeBGColor = peekWindowBGColor(context);
// TODO (b/173975965) Tracking the performance on improved splash screen.
return builder
.setContext(context)
- .setThemeDrawable(themeBGDrawable)
+ .setWindowBGColor(themeBGColor)
.setIconDrawable(iconDrawable)
.setIconAnimationDuration(animationDuration)
- .setBrandingDrawable(attrs.mBrandingImage).build();
+ .setBrandingDrawable(mTmpAttrs.mBrandingImage)
+ .setIconBackground(mTmpAttrs.mIconBgColor).build();
+ }
+
+ private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ attrs.mIconBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
}
- private static class SplashScreenWindowAttrs {
+ static class SplashScreenWindowAttrs {
private int mWindowBgResId = 0;
private int mWindowBgColor = Color.TRANSPARENT;
private Drawable mReplaceIcon = null;
private Drawable mBrandingImage = null;
+ private int mIconBgColor = Color.TRANSPARENT;
private int mAnimationDuration = 0;
-
- static SplashScreenWindowAttrs createWindowAttrs(Context context) {
- final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- attrs.mWindowBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
- attrs.mReplaceIcon = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenAnimatedIcon);
- attrs.mAnimationDuration = typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, 0);
- attrs.mBrandingImage = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenBrandingImage);
- typedArray.recycle();
- if (DEBUG) {
- Slog.d(TAG, "window attributes color: "
- + Integer.toHexString(attrs.mWindowBgColor)
- + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
- + " brandImage " + attrs.mBrandingImage);
- }
- return attrs;
- }
}
private class StartingWindowViewBuilder {
- private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
private int mIconAnimationDuration;
private Context mContext;
private Drawable mBrandingDrawable;
+ private @ColorInt int mIconBackground;
// result
private boolean mBuildComplete = false;
@@ -197,8 +222,8 @@ public class SplashscreenContentDrawer {
private Drawable mFinalIconDrawable;
private float mScale = 1f;
- StartingWindowViewBuilder setThemeDrawable(Drawable background) {
- mThemeBGDrawable = background;
+ StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ mThemeColor = background;
mBuildComplete = false;
return this;
}
@@ -221,6 +246,12 @@ public class SplashscreenContentDrawer {
return this;
}
+ StartingWindowViewBuilder setIconBackground(int color) {
+ mIconBackground = color;
+ mBuildComplete = false;
+ return this;
+ }
+
StartingWindowViewBuilder setContext(Context context) {
mContext = context;
mBuildComplete = false;
@@ -235,16 +266,14 @@ public class SplashscreenContentDrawer {
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
- if (mThemeBGDrawable == null) {
- Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
- mThemeBGDrawable = createDefaultBackgroundDrawable();
- }
- processThemeColor();
+
if (!processAdaptiveIcon() && mIconDrawable != null) {
if (DEBUG) {
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
}
- mFinalIconDrawable = mIconDrawable;
+ mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
+ mIconBackground != Color.TRANSPARENT
+ ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -252,16 +281,10 @@ public class SplashscreenContentDrawer {
return mCachedResult;
}
- private void processThemeColor() {
- final DrawableColorTester themeBGTester =
- new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
- if (themeBGTester.nonTransparentRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
- mThemeColor = getSystemBGColor();
- } else {
- mThemeColor = themeBGTester.getDominateColor();
- }
+ private void createIconDrawable(Drawable iconDrawable, int iconSize) {
+ mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
+ mIconBackground != Color.TRANSPARENT
+ ? mIconBackground : mThemeColor, iconDrawable, iconSize);
}
private boolean processAdaptiveIcon() {
@@ -269,6 +292,7 @@ public class SplashscreenContentDrawer {
return false;
}
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
final DrawableColorTester backIconTester =
new DrawableColorTester(adaptiveIconDrawable.getBackground());
@@ -305,28 +329,31 @@ public class SplashscreenContentDrawer {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
}
- // Using AdaptiveIconDrawable here can help keep the shape consistent with the
- // current settings.
- mFinalIconDrawable = new AdaptiveIconDrawable(
- new ColorDrawable(mThemeColor), iconForeground);
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
mScale = 1.5f;
}
+ // Using AdaptiveIconDrawable here can help keep the shape consistent with the
+ // current settings.
+ final int iconSize = (int) (0.5f + mIconSize * mScale);
+ createIconDrawable(iconForeground, iconSize);
} else {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
}
- mFinalIconDrawable = adaptiveIconDrawable;
+ createIconDrawable(adaptiveIconDrawable, mIconSize);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
}
private SplashScreenView fillViewWithIcon(Context context,
int iconSize, Drawable iconDrawable) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
- builder.setIconSize(iconSize).setBackgroundColor(mThemeColor);
+ builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
+ .setIconBackground(mIconBackground);
if (iconDrawable != null) {
builder.setCenterViewDrawable(iconDrawable);
}
@@ -340,6 +367,7 @@ public class SplashscreenContentDrawer {
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
splashScreenView.makeSystemUIColorsTransparent();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return splashScreenView;
}
}
@@ -371,7 +399,7 @@ public class SplashscreenContentDrawer {
return root < 0.1;
}
- private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
+ static SplashScreenView makeSplashscreenContent(Context ctx,
int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
@@ -501,6 +529,7 @@ public class SplashscreenContentDrawer {
new TransparentFilterQuantizer();
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
final Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
@@ -535,6 +564,7 @@ public class SplashscreenContentDrawer {
}
mPalette = builder.generate();
bitmap.recycle();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
new file mode 100644
index 000000000000..a4a83eb87b94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -0,0 +1,268 @@
+/*
+ * 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.startingsurface;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.PathParser;
+import android.window.SplashScreenView;
+
+import com.android.internal.R;
+
+import java.util.function.Consumer;
+
+/**
+ * Creating a lightweight Drawable object used for splash screen.
+ * @hide
+ */
+public class SplashscreenIconDrawableFactory {
+
+ static Drawable makeIconDrawable(@ColorInt int backgroundColor,
+ @NonNull Drawable foregroundDrawable, int iconSize) {
+ if (foregroundDrawable instanceof Animatable) {
+ return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
+ } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
+ return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
+ } else {
+ return new ImmobileIconDrawable(new AdaptiveIconDrawable(
+ new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
+ }
+ }
+
+ private static class ImmobileIconDrawable extends Drawable {
+ private Shader mLayersShader;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+
+ ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
+ cachePaint(drawable, iconSize, iconSize);
+ }
+
+ private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
+ final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(layersBitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ canvas.drawRect(bounds, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 1;
+ }
+ }
+
+ /**
+ * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this
+ * drawable masked by config_icon_mask.
+ * @hide
+ */
+ private static class AnimatableIconDrawable extends SplashScreenView.SplashscreenIconDrawable {
+ private static final float MASK_SIZE = AdaptiveIconDrawable.MASK_SIZE;
+ private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
+ private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
+ private final Rect mTmpOutRect = new Rect();
+ /**
+ * Clip path defined in R.string.config_icon_mask.
+ */
+ private static Path sMask;
+
+ /**
+ * Scaled mask based on the view bounds.
+ */
+ private final Path mMask;
+ private final Path mMaskScaleOnly;
+ private final Matrix mMaskMatrix;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Drawable mForegroundDrawable;
+ private Animatable mAnimatableIcon;
+ private Animator mIconAnimator;
+ private boolean mAnimationTriggered;
+ private long mIconAnimationStart;
+
+ AnimatableIconDrawable(@ColorInt int backgroundColor, Drawable foregroundDrawable) {
+ mForegroundDrawable = foregroundDrawable;
+ final Resources r = Resources.getSystem();
+ sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
+ mMask = new Path(sMask);
+ mMaskScaleOnly = new Path(mMask);
+ mMaskMatrix = new Matrix();
+ mPaint.setColor(backgroundColor);
+ mPaint.setStyle(Paint.Style.FILL);
+ if (mForegroundDrawable != null) {
+ mForegroundDrawable.setCallback(mCallback);
+ }
+ }
+
+ @Override
+ protected boolean prepareAnimate(long duration, Consumer<Long> startListener) {
+ mAnimatableIcon = (Animatable) mForegroundDrawable;
+ mIconAnimator = ValueAnimator.ofInt(0, 1);
+ mIconAnimator.setDuration(duration);
+ mIconAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIconAnimationStart = SystemClock.uptimeMillis();
+ if (startListener != null) {
+ startListener.accept(mIconAnimationStart);
+ }
+ mAnimatableIcon.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // do not repeat
+ mAnimatableIcon.stop();
+ }
+ });
+ return true;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+ updateLayerBounds(bounds);
+ }
+
+ private final Callback mCallback = new Callback() {
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
+ }
+ };
+
+ private void updateLayerBounds(Rect bounds) {
+ int cX = bounds.width() / 2;
+ int cY = bounds.height() / 2;
+
+ int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ final Rect outRect = mTmpOutRect;
+ outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
+
+ if (mForegroundDrawable != null) {
+ mForegroundDrawable.setBounds(outRect);
+ }
+ // reset everything that depends on the view bounds
+ mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
+ sMask.transform(mMaskMatrix, mMaskScaleOnly);
+ invalidateSelf();
+ }
+
+ private void ensureAnimationStarted() {
+ if (mAnimationTriggered) {
+ return;
+ }
+ if (mIconAnimator != null && !mIconAnimator.isRunning()) {
+ mIconAnimator.start();
+ }
+ mAnimationTriggered = true;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mMaskScaleOnly != null) {
+ canvas.drawPath(mMaskScaleOnly, mPaint);
+ }
+ if (mForegroundDrawable != null) {
+ ensureAnimationStarted();
+ mForegroundDrawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ if (mForegroundDrawable != null) {
+ mForegroundDrawable.setColorFilter(colorFilter);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index f258286f2d17..079d68973852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -16,35 +16,15 @@
package com.android.wm.shell.startingsurface;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.StartingWindowInfo;
-
-import java.util.function.BiConsumer;
/**
* Interface to engage starting window feature.
*/
public interface StartingSurface {
- /**
- * Called when a task need a starting window.
- */
- void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken);
- /**
- * Called when the content of a task is ready to show, starting window can be removed.
- */
- void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation);
- /**
- * Called when the Task wants to copy the splash screen.
- * @param taskId
- */
- void copySplashScreenView(int taskId);
/**
- * Registers the starting window listener.
- *
- * @param listener The callback when need a starting window.
+ * Returns a binder that can be passed to an external process to manipulate starting windows.
*/
- void setStartingWindowListener(BiConsumer<Integer, Integer> listener);
+ default IStartingWindow createExternalInterface() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 14fbaacb9613..29a144fe9808 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -36,6 +36,7 @@ import android.graphics.drawable.ColorDrawable;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -68,9 +69,6 @@ public class StartingSurfaceDrawer {
private final ShellExecutor mSplashScreenExecutor;
private final SplashscreenContentDrawer mSplashscreenContentDrawer;
- // TODO(b/131727939) remove this when clearing ActivityRecord
- private static final int REMOVE_WHEN_TIMEOUT = 2000;
-
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
TransactionPool pool) {
mContext = context;
@@ -128,6 +126,7 @@ public class StartingSurfaceDrawer {
labelRes = app.labelRes;
}
+ final int taskId = taskInfo.taskId;
Context context = mContext;
// replace with the default theme if the application didn't set
final int theme = windowInfo.splashScreenThemeResId != 0
@@ -149,11 +148,13 @@ public class StartingSurfaceDrawer {
context = displayContext;
if (theme != context.getThemeResId() || labelRes != 0) {
try {
- context = context.createPackageContext(
- activityInfo.packageName, CONTEXT_RESTRICTED);
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
- // Ignore
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
+ return;
}
}
@@ -168,25 +169,27 @@ public class StartingSurfaceDrawer {
final TypedArray typedArray = overrideContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- if (resId != 0 && overrideContext.getDrawable(resId) != null) {
- // We want to use the windowBackground for the override context if it is
- // available, otherwise we use the default one to make sure a themed starting
- // window is displayed for the app.
- if (DEBUG_SPLASH_SCREEN) {
- Slog.d(TAG, "addSplashScreen: apply overrideConfig"
- + taskConfig + " to starting window resId=" + resId);
+ try {
+ if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+ // We want to use the windowBackground for the override context if it is
+ // available, otherwise we use the default one to make sure a themed starting
+ // window is displayed for the app.
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "addSplashScreen: apply overrideConfig"
+ + taskConfig + " to starting window resId=" + resId);
+ }
+ context = overrideContext;
}
- context = overrideContext;
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
+ + taskId, e);
+ return;
}
typedArray.recycle();
}
int windowFlags = 0;
- final boolean enableHardAccelerated =
- (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0;
- if (enableHardAccelerated) {
- windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
final boolean[] showWallpaper = new boolean[1];
final int[] splashscreenContentResId = new int[1];
@@ -247,8 +250,6 @@ public class StartingSurfaceDrawer {
params.packageName = activityInfo.packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
@@ -265,24 +266,34 @@ public class StartingSurfaceDrawer {
params.setTitle("Splash Screen " + activityInfo.packageName);
// TODO(b/173975965) tracking performance
- final int taskId = taskInfo.taskId;
SplashScreenView sView = null;
try {
- sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
- splashscreenContentResId[0]);
final View view = win.getDecorView();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
- if (postAddWindow(taskId, appToken, view, wm, params)) {
+ // splash screen content will be deprecated after S.
+ sView = SplashscreenContentDrawer.makeSplashscreenContent(
+ context, splashscreenContentResId[0]);
+ final boolean splashscreenContentCompatible = sView != null;
+ if (splashscreenContentCompatible) {
+ win.setContentView(sView);
+ } else {
+ sView = mSplashscreenContentDrawer
+ .makeSplashScreenContentView(context, activityInfo);
win.setContentView(sView);
sView.cacheRootWindow(win);
}
+ postAddWindow(taskId, appToken, view, wm, params);
} catch (RuntimeException e) {
// don't crash if something else bad happens, for example a
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
- Slog.w(TAG, " failed creating starting window", e);
+ Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
+ sView = null;
} finally {
- setSplashScreenRecord(taskId, sView);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.setSplashScreenView(sView);
+ }
}
}
@@ -293,12 +304,8 @@ public class StartingSurfaceDrawer {
TaskSnapshot snapshot) {
final int taskId = startingWindowInfo.taskInfo.taskId;
final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
- snapshot, mSplashScreenExecutor,
- () -> removeWindowNoAnimate(taskId));
- mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId),
- REMOVE_WHEN_TIMEOUT);
- final StartingWindowRecord tView =
- new StartingWindowRecord(null/* decorView */, surface);
+ snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
+ final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface);
mStartingWindowRecords.put(taskId, tView);
}
@@ -333,7 +340,7 @@ public class StartingSurfaceDrawer {
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
- protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
boolean shouldSaveView = true;
try {
@@ -352,11 +359,8 @@ public class StartingSurfaceDrawer {
}
if (shouldSaveView) {
removeWindowNoAnimate(taskId);
- mSplashScreenExecutor.executeDelayed(
- () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT);
saveSplashScreenRecord(taskId, view);
}
- return shouldSaveView;
}
private void saveSplashScreenRecord(int taskId, View view) {
@@ -365,14 +369,6 @@ public class StartingSurfaceDrawer {
mStartingWindowRecords.put(taskId, tView);
}
- private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- record.setSplashScreenView(splashScreenView);
- splashScreenView.startIntroAnimation();
- }
- }
-
private void removeWindowNoAnimate(int taskId) {
removeWindowSynced(taskId, null, null, false);
}
@@ -386,15 +382,16 @@ public class StartingSurfaceDrawer {
Slog.v(TAG, "Removing splash screen window for task: " + taskId);
}
if (record.mContentView != null) {
- final HandleExitFinish exitFinish = new HandleExitFinish(record.mDecorView);
if (leash != null || playRevealAnimation) {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
- leash, frame, record.isEarlyExit(), exitFinish);
- mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT);
+ leash, frame, record.isEarlyExit(),
+ () -> removeWindowInner(record.mDecorView, true));
} else {
+ // TODO(183004107) Always hide decorView when playRevealAnimation is enabled
+ // from TaskOrganizerController#removeStartingWindow
// the SplashScreenView has been copied to client, skip default exit
// animation
- exitFinish.run();
+ removeWindowInner(record.mDecorView, false);
}
}
}
@@ -408,23 +405,13 @@ public class StartingSurfaceDrawer {
}
}
- private static class HandleExitFinish implements Runnable {
- private View mDecorView;
-
- HandleExitFinish(View decorView) {
- mDecorView = decorView;
+ private void removeWindowInner(View decorView, boolean hideView) {
+ if (hideView) {
+ decorView.setVisibility(View.GONE);
}
-
- @Override
- public void run() {
- if (mDecorView == null) {
- return;
- }
- final WindowManager wm = mDecorView.getContext().getSystemService(WindowManager.class);
- if (wm != null) {
- wm.removeView(mDecorView);
- }
- mDecorView = null;
+ final WindowManager wm = decorView.getContext().getSystemService(WindowManager.class);
+ if (wm != null) {
+ wm.removeView(decorView);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index a694e525a761..a06068d6c497 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -25,6 +25,8 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -37,6 +39,9 @@ import android.window.StartingWindowInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -58,7 +63,7 @@ import java.util.function.BiConsumer;
* constructor to keep everything synchronized.
* @hide
*/
-public class StartingWindowController {
+public class StartingWindowController implements RemoteCallable<StartingWindowController> {
private static final String TAG = StartingWindowController.class.getSimpleName();
static final boolean DEBUG_SPLASH_SCREEN = false;
static final boolean DEBUG_TASK_SNAPSHOT = false;
@@ -68,17 +73,17 @@ public class StartingWindowController {
private BiConsumer<Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
+ private final Context mContext;
private final ShellExecutor mSplashScreenExecutor;
// For Car Launcher
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) {
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
- new TransactionPool());
- mSplashScreenExecutor = splashScreenExecutor;
+ this(context, splashScreenExecutor, new TransactionPool());
}
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
TransactionPool pool) {
+ mContext = context;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
mSplashScreenExecutor = splashScreenExecutor;
}
@@ -90,6 +95,16 @@ public class StartingWindowController {
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mSplashScreenExecutor;
+ }
+
private static class StartingTypeChecker {
TaskSnapshot mSnapshot;
@@ -150,6 +165,13 @@ public class StartingWindowController {
}
return false;
}
+ if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) {
+ if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+ Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot "
+ + windowInfo.taskInfo.topActivity);
+ }
+ return false;
+ }
final int taskRotation = windowInfo.taskInfo.configuration
.windowConfiguration.getRotation();
@@ -188,59 +210,121 @@ public class StartingWindowController {
/**
* Called when a task need a starting window.
*/
- void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
- final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
- final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (mTaskLaunchingCallback != null) {
- mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
- }
- if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
- } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
- final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
- }
- // If prefer don't show, then don't show!
+ public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ mSplashScreenExecutor.execute(() -> {
+ final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
+ final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
+ if (mTaskLaunchingCallback != null) {
+ mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
+ }
+ if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
+ } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
+ final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
+ }
+ // If prefer don't show, then don't show!
+ });
}
- void copySplashScreenView(int taskId) {
- mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ public void copySplashScreenView(int taskId) {
+ mSplashScreenExecutor.execute(() -> {
+ mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ });
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
- void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
+ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
- mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ mSplashScreenExecutor.execute(() -> {
+ mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ });
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
private class StartingSurfaceImpl implements StartingSurface {
+ private IStartingWindowImpl mIStartingWindow;
@Override
- public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.addStartingWindow(windowInfo, appToken));
+ public IStartingWindowImpl createExternalInterface() {
+ if (mIStartingWindow != null) {
+ mIStartingWindow.invalidate();
+ }
+ mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
+ return mIStartingWindow;
}
+ }
- @Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.removeStartingWindow(taskId, leash, frame,
- playRevealAnimation));
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private StartingWindowController mController;
+ private IStartingWindowListener mListener;
+ private final BiConsumer<Integer, Integer> mStartingWindowListener =
+ this::notifyIStartingWindowListener;
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final StartingWindowController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.setStartingWindowListener(null);
+ });
+ }
+ };
+
+ public IStartingWindowImpl(StartingWindowController controller) {
+ mController = controller;
}
- @Override
- public void copySplashScreenView(int taskId) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.copySplashScreenView(taskId));
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
}
@Override
- public void setStartingWindowListener(BiConsumer<Integer, Integer> listener) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.setStartingWindowListener(listener));
+ public void setStartingWindowListener(IStartingWindowListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener",
+ (controller) -> {
+ if (mListener != null) {
+ // Reset the old death recipient
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.setStartingWindowListener(mStartingWindowListener);
+ });
+ }
+
+ private void notifyIStartingWindowListener(int taskId, int supportedType) {
+ if (mListener == null) {
+ return;
+ }
+
+ try {
+ mListener.onTaskLaunching(taskId, supportedType);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify task launching", e);
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 629ff0db6b4a..b29b18bec032 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -96,6 +96,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
};
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getMode() == TRANSIT_CHANGE) {
+ // No default animation for this, so just update bounds/position.
+ t.setPosition(change.getLeash(),
+ change.getEndAbsBounds().left - change.getEndRelOffset().x,
+ change.getEndAbsBounds().top - change.getEndRelOffset().y);
+ if (change.getTaskInfo() != null) {
+ // Skip non-tasks since those usually have null bounds.
+ t.setWindowCrop(change.getLeash(),
+ change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
+ }
+ }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
new file mode 100644
index 000000000000..dffc700a3690
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.transition;
+
+import android.window.IRemoteTransition;
+import android.window.TransitionFilter;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the transitions feature.
+ */
+interface IShellTransitions {
+
+ /**
+ * Registers a remote transition handler.
+ */
+ oneway void registerRemote(in TransitionFilter filter,
+ in IRemoteTransition remoteTransition) = 1;
+
+ /**
+ * Unregisters a remote transition handler.
+ */
+ oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
new file mode 100644
index 000000000000..71fd917275ea
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -0,0 +1,107 @@
+/*
+ * 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.transition;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+/**
+ * Handler that forwards to a RemoteTransition. It is designed for one-shot use to attach a
+ * specific remote animation to a specific transition.
+ */
+public class OneShotRemoteHandler implements Transitions.TransitionHandler {
+ private final ShellExecutor mMainExecutor;
+
+ /** The specific transition that this handler is associated with. Just for validation. */
+ private IBinder mTransition = null;
+
+ /** The remote to delegate animation to */
+ private final IRemoteTransition mRemote;
+
+ public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
+ @NonNull IRemoteTransition remote) {
+ mMainExecutor = mainExecutor;
+ mRemote = remote;
+ }
+
+ public void setTransition(@NonNull IBinder transition) {
+ mTransition = transition;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mTransition != transition) return false;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
+ + " transition %s for %s.", mRemote, transition);
+
+ final IBinder.DeathRecipient remoteDied = () -> {
+ Log.e(Transitions.TAG, "Remote transition died, finishing");
+ mMainExecutor.execute(
+ () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+ };
+ IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction wct) {
+ if (mRemote.asBinder() != null) {
+ mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
+ }
+ mMainExecutor.execute(
+ () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+ }
+ };
+ try {
+ if (mRemote.asBinder() != null) {
+ mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
+ }
+ mRemote.startAnimation(info, t, cb);
+ } catch (RemoteException e) {
+ if (mRemote.asBinder() != null) {
+ mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
+ }
+ Log.e(Transitions.TAG, "Error running remote transition.", e);
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ }
+ return true;
+ }
+
+ @Override
+ @Nullable
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ IRemoteTransition remote = request.getRemoteTransition();
+ if (remote != mRemote) return null;
+ mTransition = transition;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
+ + " for %s: %s", transition, remote);
+ return new WindowContainerTransaction();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ac93a17b4014..8876e5f86139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.transition;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
@@ -23,6 +25,7 @@ import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -31,6 +34,8 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -42,6 +47,8 @@ import java.util.ArrayList;
* if the request includes a specific remote.
*/
public class RemoteTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "RemoteTransitionHandler";
+
private final ShellExecutor mMainExecutor;
/** Includes remotes explicitly requested by, eg, ActivityOptions */
@@ -51,20 +58,42 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters =
new ArrayList<>();
+ private final IBinder.DeathRecipient mTransitionDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ mMainExecutor.execute(() -> {
+ mFilters.clear();
+ });
+ }
+ };
+
RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
}
void addFiltered(TransitionFilter filter, IRemoteTransition remote) {
+ try {
+ remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
mFilters.add(new Pair<>(filter, remote));
}
void removeFiltered(IRemoteTransition remote) {
+ boolean removed = false;
for (int i = mFilters.size() - 1; i >= 0; --i) {
if (mFilters.get(i).second == remote) {
mFilters.remove(i);
+ removed = true;
}
}
+ if (removed) {
+ remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index 85bbf74d56b9..bc42c6e2f12c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -26,7 +26,15 @@ import com.android.wm.shell.common.annotations.ExternalThread;
* Interface to manage remote transitions.
*/
@ExternalThread
-public interface RemoteTransitions {
+public interface ShellTransitions {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate remote transitions.
+ */
+ default IShellTransitions createExternalInterface() {
+ return null;
+ }
+
/**
* Registers a remote transition.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 677db10d07a7..ca1b53d4d46b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,6 +23,8 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -51,6 +53,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -60,7 +63,7 @@ import java.util.ArrayList;
import java.util.Arrays;
/** Plays transition animations */
-public class Transitions {
+public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
@@ -73,7 +76,7 @@ public class Transitions {
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
private final RemoteTransitionHandler mRemoteTransitionHandler;
- private final RemoteTransitionImpl mImpl = new RemoteTransitionImpl();
+ private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -87,10 +90,6 @@ public class Transitions {
/** Keeps track of currently tracked transitions and all the animations associated with each */
private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>();
- public static RemoteTransitions asRemoteTransitions(Transitions transitions) {
- return transitions.mImpl;
- }
-
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull Context context, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
@@ -126,6 +125,20 @@ public class Transitions {
mRemoteTransitionHandler = null;
}
+ public ShellTransitions asRemoteTransitions() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void dispatchAnimScaleSetting(float scale) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
mHandlers.get(i).setAnimScaleSetting(scale);
@@ -134,8 +147,8 @@ public class Transitions {
/** Create an empty/non-registering transitions object for system-ui tests. */
@VisibleForTesting
- public static RemoteTransitions createEmptyForTesting() {
- return new RemoteTransitions() {
+ public static ShellTransitions createEmptyForTesting() {
+ return new ShellTransitions() {
@Override
public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
@androidx.annotation.NonNull IRemoteTransition remoteTransition) {
@@ -426,24 +439,74 @@ public class Transitions {
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
@ExternalThread
- private class RemoteTransitionImpl implements RemoteTransitions {
+ private class ShellTransitionImpl implements ShellTransitions {
+ private IShellTransitionsImpl mIShellTransitions;
+
+ @Override
+ public IShellTransitions createExternalInterface() {
+ if (mIShellTransitions != null) {
+ mIShellTransitions.invalidate();
+ }
+ mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
+ return mIShellTransitions;
+ }
+
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
- Transitions.this.registerRemote(filter, remoteTransition);
+ mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
});
}
@Override
public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
- Transitions.this.unregisterRemote(remoteTransition);
+ mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
}
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private Transitions mTransitions;
+
+ IShellTransitionsImpl(Transitions transitions) {
+ mTransitions = transitions;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mTransitions = null;
+ }
+
+ @Override
+ public void registerRemote(@NonNull TransitionFilter filter,
+ @NonNull IRemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
+ (transitions) -> {
+ transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
+ });
+ }
+
+ @Override
+ public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
+ (transitions) -> {
+ transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
+ });
+ }
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 35bab7aaf22c..fcd333f13868 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -73,7 +73,8 @@ fun FlickerTestParameter.dockedStackDividerIsInvisible() {
fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) {
assertLayersEnd {
val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName)
+ visibleRegion(primaryLayerName)
+ .coversExactly(getPrimaryRegion(dividerRegion, rotation))
}
}
@@ -83,7 +84,8 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible(
) {
assertLayersEnd {
val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER)
- this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName)
+ visibleRegion(primaryLayerName)
+ .coversExactly(getPrimaryRegion(dividerRegion, rotation))
}
}
@@ -93,7 +95,8 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible(
) {
assertLayersEnd {
val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName)
+ visibleRegion(secondaryLayerName)
+ .coversExactly(getSecondaryRegion(dividerRegion, rotation))
}
}
@@ -103,7 +106,8 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible(
) {
assertLayersEnd {
val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER)
- this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName)
+ visibleRegion(secondaryLayerName)
+ .coversExactly(getSecondaryRegion(dividerRegion, rotation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
deleted file mode 100644
index 1869d833fbc4..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.
- */
-
-@file:JvmName("Utils")
-package com.android.wm.shell.flicker
-
-import android.app.ActivityTaskManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
-import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
-
-fun removeAllTasksButHome() {
- val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
- ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
- ACTIVITY_TYPE_UNDEFINED)
- val atm = ActivityTaskManager.getService()
- atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
-}
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 90e71373b1fd..98ce2747d532 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,8 @@ 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
@@ -25,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
@@ -32,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
@@ -46,6 +49,7 @@ import org.junit.runners.Parameterized
class AppPairsTestCannotPairNonResizeableApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -59,6 +63,32 @@ 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()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index dc51b4fb5a9e..614530b5e469 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -56,6 +56,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
}
}
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
@@ -74,10 +82,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
fun appsEndingBounds() {
testSpec.assertLayersEnd {
val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- this.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion),
- primaryApp.defaultWindowName)
- .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion),
- secondaryApp.defaultWindowName)
+ visibleRegion(primaryApp.defaultWindowName)
+ .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
+ visibleRegion(secondaryApp.defaultWindowName)
+ .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
}
}
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 000000000000..1e3595c17f48
--- /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<String, Any?>) -> 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<FlickerTestParameter> {
+ 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/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 5bb9b2f8b8ca..ef68ed630353 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -61,6 +61,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
}
}
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible()
@@ -79,10 +87,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
fun appsStartingBounds() {
testSpec.assertLayersStart {
val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion),
- primaryApp.defaultWindowName)
- coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion),
- secondaryApp.defaultWindowName)
+ visibleRegion(primaryApp.defaultWindowName)
+ .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
+ visibleRegion(secondaryApp.defaultWindowName)
+ .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
}
}
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 91e080f65550..134d00be73e8 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,22 +17,27 @@
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
+import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -42,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,
@@ -134,17 +140,39 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter)
@Presubmit
@Test
- open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ open fun navBarLayerIsAlwaysVisible() {
+ testSpec.navBarLayerIsAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ open fun statusBarLayerIsAlwaysVisible() {
+ testSpec.statusBarLayerIsAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ open fun navBarWindowIsAlwaysVisible() {
+ testSpec.navBarWindowIsAlwaysVisible()
+ }
@Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() {
+ testSpec.statusBarWindowIsAlwaysVisible()
+ }
@Presubmit
@Test
- open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ open fun navBarLayerRotatesAndScales() {
+ testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0,
+ testSpec.config.endRotation)
+ }
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarLayerRotatesScales() {
+ testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
+ testSpec.config.endRotation)
+ }
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index 5f003ba62b2d..d341bb1e6aa9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -27,8 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.appPairsDividerIsVisible
import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible
@@ -75,16 +73,6 @@ class RotateTwoLaunchedAppsInAppPairsMode(
@Test
fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
-
- @Presubmit
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
-
@FlakyTest(bugId = 172776659)
@Test
fun appPairsPrimaryBoundsIsVisible() =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index d4792088ac31..3bf0296fee20 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -27,16 +27,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.appPairsDividerIsVisible
import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,20 +64,10 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
@Presubmit
@Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
-
- @Presubmit
- @Test
fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(
- Surface.ROTATION_0, testSpec.config.endRotation)
-
- @Presubmit
- @Test
override fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 5a96a7c8cbd9..f4dd7decb1b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -26,8 +26,6 @@ import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
-import com.android.wm.shell.flicker.pip.waitPipWindowGone
-import com.android.wm.shell.flicker.pip.waitPipWindowShown
import com.android.wm.shell.flicker.testapp.Components
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
@@ -64,7 +62,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
stringExtras: Map<String, String>
) {
super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
- wmHelper.waitPipWindowShown()
+ wmHelper.waitFor { it.wmState.hasPipWindow() }
}
private fun focusOnObject(selector: BySelector): Boolean {
@@ -86,7 +84,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
clickObject(ENTER_PIP_BUTTON_ID)
// Wait on WMHelper or simply wait for 3 seconds
- wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000)
+ wmHelper?.waitFor { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000)
}
fun clickStartMediaSessionButton() {
@@ -139,7 +137,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
// Wait for animation to complete.
- wmHelper.waitPipWindowGone()
+ wmHelper.waitFor { !it.wmState.hasPipWindow() }
wmHelper.waitForHomeActivityVisible()
}
@@ -169,7 +167,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val windowRect = windowRegion.bounds
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
- wmHelper.waitPipWindowGone()
+ wmHelper.waitFor { !it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index 17c51fb15b0c..bca257646e11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -29,8 +29,7 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -60,6 +59,11 @@ class EnterSplitScreenDockActivity(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 169271943)
@Test
fun dockedStackPrimaryBoundsIsVisible() =
@@ -73,12 +77,8 @@ class EnterSplitScreenDockActivity(
@FlakyTest(bugId = 178531736)
@Test
// b/178531736
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -91,12 +91,8 @@ class EnterSplitScreenDockActivity(
@FlakyTest(bugId = 178531736)
@Test
// b/178531736
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
index a94fd463c624..9000f22fb03d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
@@ -62,6 +61,11 @@ class EnterSplitScreenLaunchToSide(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 169271943)
@Test
fun dockedStackPrimaryBoundsIsVisible() =
@@ -83,12 +87,8 @@ class EnterSplitScreenLaunchToSide(
@Test
// TODO(b/178447631) Remove Splash Screen from white list when flicker lib
// add a wait for splash screen be gone
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -104,12 +104,8 @@ class EnterSplitScreenLaunchToSide(
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
index 238059b484b5..7d22d4dbe5ab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -16,21 +16,23 @@
package com.android.wm.shell.flicker.legacysplitscreen
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
import android.view.Surface
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.WALLPAPER_TITLE
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.canSplitScreen
import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
import org.junit.Assert
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,27 +40,31 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test open non-resizable activity will auto exit split screen mode
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
+ * Test enter split screen from non-resizable activity. When the device doesn't support
+ * non-resizable in multi window, there should be no button to enter split screen for non-resizable
+ * activity.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable`
*/
+@Postsubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FlakyTest(bugId = 173875043)
-class EnterSplitScreenNonResizableNotDock(
+class EnterSplitScreenNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
- super.transition(this, configuration)
- teardown {
+ cleanSetup(this, configuration)
+ setup {
eachRun {
- nonResizeableApp.exit(wmHelper)
+ nonResizeableApp.launchViaIntent(wmHelper)
}
}
transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
device.openQuickstep(wmHelper)
if (device.canSplitScreen(wmHelper)) {
Assert.fail("Non-resizeable app should not enter split screen")
@@ -66,28 +72,42 @@ class EnterSplitScreenNonResizableNotDock(
}
}
- @Test
- fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+
+ @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(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- SPLASH_SCREEN_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(WALLPAPER_TITLE,
- LAUNCHER_PACKAGE_NAME,
- SPLASH_SCREEN_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
@Test
fun appWindowIsVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
new file mode 100644
index 000000000000..9b4a10389619
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+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.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 enter split screen from non-resizable activity. When the device supports
+ * non-resizable in multi window, there should be a button to enter split screen for non-resizable
+ * activity.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class EnterSplitScreenSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow != 1) {
+ // 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(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+
+ @Test
+ fun appWindowIsVisible() {
+ testSpec.assertWmEnd {
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index acd570a3773e..9717709852d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -70,17 +69,20 @@ class ExitLegacySplitScreenFromBottom(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@Presubmit
@Test
fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER)
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
@Presubmit
@Test
@@ -97,11 +99,8 @@ class ExitLegacySplitScreenFromBottom(
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
index cef188695ce7..3f714bb6b6c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -30,8 +30,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -70,6 +69,11 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 175687842)
@Test
fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
@@ -80,11 +84,8 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen(
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -101,11 +102,8 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen(
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
new file mode 100644
index 000000000000..892384561eb2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+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.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 launch non-resizable activity via intent in split screen mode. When the device does not
+ * support non-resizable in multi window, it should trigger exit split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromIntentNotSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @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(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun resizableAppLayerBecomesInvisible() =
+ testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun resizableAppWindowBecomesInvisible() =
+ testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+
+ @Test
+ fun onlyNonResizableAppWindowIsVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isInvisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
index 543484ac9759..2f5e0bddd71f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -16,23 +16,24 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
import android.view.Surface
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.appWindowBecomesInVisible
import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,18 +41,20 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test launch non resizable activity in split screen mode will trigger exit split screen mode
- * (Non resizable activity launch via intent)
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
+ * Test launch non-resizable activity via intent in split screen mode. When the device supports
+ * non-resizable in multi window, it should show the non-resizable app in split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable`
*/
-@Presubmit
+@Postsubmit
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreen(
+class LegacySplitScreenFromIntentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
cleanSetup(this, configuration)
@@ -67,45 +70,58 @@ class NonResizableLaunchInLegacySplitScreen(
}
}
- @Presubmit
- @Test
- fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @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)
+ }
- @Presubmit
+ @FlakyTest(bugId = 178447631)
@Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER,
- LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- @Presubmit
@Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
- @Presubmit
@Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
- @FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER,
- LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
+ fun bothAppsWindowsAreVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isVisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
new file mode 100644
index 000000000000..a42774d93b5b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+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.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+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 launch non-resizable activity via recent overview in split screen mode. When the device does
+ * not support non-resizable in multi window, it should trigger exit split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromRecentNotSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ device.reopenAppFromOverview(wmHelper)
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @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(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun resizableAppLayerBecomesInvisible() =
+ testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun resizableAppWindowBecomesInvisible() =
+ testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+
+ @Test
+ fun onlyNonResizableAppWindowIsVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isInvisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
index caafa278d297..14f6deef6ff4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -16,24 +16,25 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
import android.view.Surface
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.appWindowBecomesInVisible
import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,17 +42,20 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test launch non resizable activity in split screen mode will trigger exit split screen mode
- * (Non resizable activity launch via recent overview)
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
+ * Test launch non-resizable activity via recent overview in split screen mode. When the device
+ * supports non-resizable in multi window, it should show the non-resizable app in split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable`
*/
+@Postsubmit
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreen(
+class LegacySplitScreenFromRecentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
cleanSetup(this, configuration)
@@ -67,43 +71,58 @@ class NonResizableDismissInLegacySplitScreen(
}
}
- @Presubmit
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @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(bugId = 178447631)
@Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- @Presubmit
@Test
- fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
- @Presubmit
@Test
- fun appWindowBecomesVisible() =
+ fun nonResizableAppWindowBecomesVisible() =
testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
- @Presubmit
@Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
- @FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName)
- )
+ fun bothAppsWindowsAreVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isVisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
index 1e89a25c06df..08d5db0f9124 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
@@ -17,11 +17,13 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.view.Surface
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import org.junit.Test
abstract class LegacySplitScreenRotateTransition(
testSpec: FlickerTestParameter
@@ -44,4 +46,16 @@ abstract class LegacySplitScreenRotateTransition(
}
}
}
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 7f69a66e6e82..72d6f569ab0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -34,14 +34,13 @@ import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
@@ -89,6 +88,10 @@ class LegacySplitScreenToLauncher(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(launcherPackageName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@Presubmit
@Test
fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
@@ -99,11 +102,6 @@ class LegacySplitScreenToLauncher(
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
@Presubmit
@@ -122,8 +120,8 @@ class LegacySplitScreenToLauncher(
@Presubmit
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName))
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index 91ea8716e4f0..e13056c36684 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.app.Instrumentation
+import android.content.Context
+import android.platform.test.annotations.Presubmit
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,10 +31,13 @@ import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Test
abstract class LegacySplitScreenTransition(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 splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
@@ -40,6 +45,15 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
.launcherStrategy.supportedLauncherPackage
+ /**
+ * List of windows that are ignored when verifying that visible elements appear on 2
+ * consecutive entries in the trace.
+ *
+ * b/182720234
+ */
+ open val ignoredWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
setup {
@@ -88,11 +102,26 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
}
}
+ @Presubmit
+ @Test
+ open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows)
+ }
+ }
+
+ @Presubmit
+ @Test
+ open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows)
+ }
+ }
+
companion object {
internal const val LIVE_WALLPAPER_PACKAGE_NAME =
"com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
internal const val LETTERBOX_NAME = "Letterbox"
internal const val TOAST_NAME = "Toast"
- internal const val SPLASH_SCREEN_NAME = "Splash Screen"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
index d22833784bcf..8f15e5088914 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -31,8 +31,7 @@ import com.android.server.wm.flicker.layerBecomesVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -61,12 +60,15 @@ class OpenAppToLegacySplitScreen(
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -90,10 +92,8 @@ class OpenAppToLegacySplitScreen(
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@FlakyTest(bugId = 151179149)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index f5174bc3cef7..33ade38d0373 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -40,8 +40,6 @@ import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
@@ -107,8 +105,11 @@ class ResizeLegacySplitScreen(
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+ }
@FlakyTest(bugId = 156223549)
@Test
@@ -144,8 +145,8 @@ class ResizeLegacySplitScreen(
testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Test
fun topAppLayerIsAlwaysVisible() {
@@ -182,8 +183,8 @@ class ResizeLegacySplitScreen(
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
- this.coversExactly(topAppBounds, "SimpleActivity")
- .coversExactly(bottomAppBounds, "ImeActivity")
+ visibleRegion("SimpleActivity").coversExactly(topAppBounds)
+ visibleRegion("ImeActivity").coversExactly(bottomAppBounds)
}
}
@@ -202,8 +203,8 @@ class ResizeLegacySplitScreen(
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
- this.coversExactly(topAppBounds, sSimpleActivity)
- .coversExactly(bottomAppBounds, sImeActivity)
+ visibleRegion(sSimpleActivity).coversExactly(topAppBounds)
+ visibleRegion(sImeActivity).coversExactly(bottomAppBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index 75c33c671008..1ba1f3b3eb5d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -23,13 +23,7 @@ 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.server.wm.flicker.helpers.WindowUtils
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,7 +42,6 @@ class EnterExitPipTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
private val testApp = FixedAppHelper(instrumentation)
- private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
@@ -84,14 +77,6 @@ class EnterExitPipTest(
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
fun showBothAppLayersThenHidePip() {
testSpec.assertLayers {
isVisible(testApp.defaultWindowName)
@@ -105,8 +90,8 @@ class EnterExitPipTest(
@Test
fun testAppCoversFullScreenWithPipOnDisplay() {
testSpec.assertLayersStart {
- coversExactly(displayBounds, testApp.defaultWindowName)
- coversAtMost(displayBounds, pipApp.defaultWindowName)
+ visibleRegion(testApp.defaultWindowName).coversExactly(displayBounds)
+ visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds)
}
}
@@ -114,18 +99,10 @@ class EnterExitPipTest(
@Test
fun pipAppCoversFullScreen() {
testSpec.assertLayersEnd {
- coversExactly(displayBounds, pipApp.defaultWindowName)
+ visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds)
}
}
- @Presubmit
- @Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 2f08db1b7d0a..cd20ddee04fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -18,20 +18,11 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
-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.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.startRotation
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,15 +48,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun pipWindowBecomesVisible() {
+ fun pipAppWindowAlwaysVisible() {
testSpec.assertWm {
this.showsAppWindow(pipApp.defaultWindowName)
}
@@ -73,34 +56,21 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @Presubmit
- @Test
fun pipLayerBecomesVisible() {
testSpec.assertLayers {
this.isVisible(pipApp.launcherName)
}
}
- @FlakyTest(bugId = 140855415)
- @Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @FlakyTest(bugId = 140855415)
- @Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
- fun noUncoveredRegions() =
- testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun pipWindowBecomesVisible() {
+ testSpec.assertWm {
+ invoke("pipWindowIsNotVisible") { !it.wmState.hasPipWindow() }
+ .then()
+ .invoke("pipWindowIsVisible") { it.wmState.hasPipWindow() }
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 9011f1a9fb6a..2beec2e8c1ed 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -18,16 +18,13 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
+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.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
@@ -78,11 +75,19 @@ class EnterPipToOtherOrientationTest(
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- wmHelper.waitPipWindowShown()
+ wmHelper.waitFor { it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
}
}
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun pipAppWindowIsAlwaysOnTop() {
@@ -109,17 +114,9 @@ class EnterPipToOtherOrientationTest(
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
fun pipAppLayerHidesTestApp() {
testSpec.assertLayersStart {
- coversExactly(startingBounds, pipApp.defaultWindowName)
+ visibleRegion(pipApp.defaultWindowName).coversExactly(startingBounds)
isInvisible(testApp.defaultWindowName)
}
}
@@ -128,18 +125,10 @@ class EnterPipToOtherOrientationTest(
@Test
fun testAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
- coversExactly(endingBounds, testApp.defaultWindowName)
+ visibleRegion(testApp.defaultWindowName).coversExactly(endingBounds)
}
}
- @Presubmit
- @Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
index 96eb66c3cc28..0037059e2c51 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
@@ -16,62 +16,14 @@
package com.android.wm.shell.flicker.pip
-import android.app.WindowConfiguration
import android.content.ComponentName
-import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.parser.toWindowName
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.google.common.truth.Truth
-
-inline val WindowManagerState.pinnedWindows
- get() = visibleWindows
- .filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED }
-
-/**
- * Checks if the state has any window in PIP mode
- */
-fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
/**
* Checks that an activity [activity] is in PIP mode
*/
fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean {
val windowName = activity.toWindowName()
- return pinnedWindows.any { it.title == windowName }
+ return isInPipMode(windowName)
}
-
-/**
- * Asserts that an activity [activity] exists and is in PIP mode
- */
-fun WindowManagerStateSubject.isInPipMode(
- activity: ComponentName
-): WindowManagerStateSubject = apply {
- val windowName = activity.toWindowName()
- contains(windowName)
- val pinnedWindows = wmState.pinnedWindows
- .map { it.title }
- Truth.assertWithMessage("Window not in PIP mode")
- .that(pinnedWindows)
- .contains(windowName)
-}
-
-/**
- * Waits until the state has a window in PIP mode, i.e., with
- * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
- */
-fun WindowManagerStateHelper.waitPipWindowShown(): Boolean =
- waitFor("PIP window shown") {
- val result = it.wmState.hasPipWindow()
- result
- }
-
-/**
- * Waits until the state doesn't have a window in PIP mode, i.e., with
- * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
- */
-fun WindowManagerStateHelper.waitPipWindowGone(): Boolean =
- waitFor("PIP window gone") {
- val result = !it.wmState.hasPipWindow()
- result
- }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
index 3e331761f767..eae7e973711c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
@@ -24,14 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.Test
import org.junit.runners.Parameterized
@@ -52,22 +45,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio
@Presubmit
@Test
- open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
open fun pipWindowBecomesInvisible() {
testSpec.assertWm {
this.showsAppWindow(PIP_WINDOW_TITLE)
@@ -86,21 +63,6 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio
}
}
- @Presubmit
- @Test
- open fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @Presubmit
- @Test
- open fun noUncoveredRegions() =
- testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @Presubmit
- @Test
- open fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
-
@FlakyTest(bugId = 151179149)
@Test
open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
index afaf33a7c46f..c7a1c9aac86b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -51,40 +51,40 @@ class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
override fun noUncoveredRegions() = super.noUncoveredRegions()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 97afc65b8b61..afba508f54b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,18 +24,12 @@ 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.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.removeAllTasksButHome
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,7 +49,6 @@ import org.junit.runners.Parameterized
class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val imeApp = ImeAppHelper(instrumentation)
private val testApp = FixedAppHelper(instrumentation)
- private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -85,7 +78,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowInsideDisplayBounds() {
testSpec.assertWm {
@@ -93,7 +86,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
@@ -103,15 +96,15 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
- @Postsubmit
+ @Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerInsideDisplayBounds() {
testSpec.assertLayers {
@@ -119,22 +112,22 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppLayersVisible() {
testSpec.assertLayersEnd {
- coversAtMost(displayBounds, testApp.defaultWindowName)
- coversAtMost(displayBounds, imeApp.defaultWindowName)
+ visibleRegion(testApp.defaultWindowName).coversAtMost(displayBounds)
+ visibleRegion(imeApp.defaultWindowName).coversAtMost(displayBounds)
}
}
- @Postsubmit
+ @Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
companion object {
const val TEST_REPETITIONS = 2
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
index 4c95da284d9e..5713822bba99 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
@@ -16,16 +16,15 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.launcher3.tapl.LauncherInstrumentation
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.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,19 +56,19 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec
}
}
- @Postsubmit
+ @Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Postsubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) }
- @Postsubmit
+ @Presubmit
@Test
- fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) }
+ fun pipLayerInsideDisplay() {
+ testSpec.assertLayersStart {
+ visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds)
+ }
+ }
- @Postsubmit
+ @FlakyTest(bugId = 184050344)
@Test
fun pipWindowMovesUp() = testSpec.assertWmEnd {
val initialState = this.trace?.first()?.wmState
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index df835d21e73f..852ee4726080 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -27,15 +26,11 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -77,52 +72,36 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
+ override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
testSpec.config.endRotation, allStates = false)
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
- @FlakyTest(bugId = 140855415)
- @Test
- fun navBarLayerRotatesAndScales() =
+ override fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
testSpec.config.endRotation)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() =
+ override fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
testSpec.config.endRotation)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun appLayerRotates_StartingBounds() {
testSpec.assertLayersStart {
- coversExactly(startingBounds, fixedApp.defaultWindowName)
- coversAtMost(startingBounds, pipApp.defaultWindowName)
+ visibleRegion(fixedApp.defaultWindowName).coversExactly(startingBounds)
+ visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun appLayerRotates_EndingBounds() {
testSpec.assertLayersEnd {
- coversExactly(endingBounds, fixedApp.defaultWindowName)
- coversAtMost(endingBounds, pipApp.defaultWindowName)
+ visibleRegion(fixedApp.defaultWindowName).coversExactly(endingBounds)
+ visibleRegion(pipApp.defaultWindowName).coversAtMost(endingBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 1bb1d2861f3f..6f17a2c57ffc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -26,14 +26,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,22 +61,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
fun appReplacesPipWindow() {
testSpec.assertWm {
this.showsAppWindow(PIP_WINDOW_TITLE)
@@ -94,11 +71,6 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @Presubmit
- @Test
fun appReplacesPipLayer() {
testSpec.assertLayers {
this.isVisible(PIP_WINDOW_TITLE)
@@ -107,15 +79,13 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
}
}
- @Presubmit
- @Test
- fun noUncoveredRegions() =
- testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
-
- @Presubmit
+ @FlakyTest
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun testAppCoversFullScreen() {
+ testSpec.assertLayersStart {
+ visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds)
+ }
+ }
@FlakyTest(bugId = 151179149)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index b0a9afef9215..ad1ccbd10753 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,27 +18,37 @@ package com.android.wm.shell.flicker.pip
import android.app.Instrumentation
import android.content.Intent
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.removeAllTasksButHome
import com.android.wm.shell.flicker.testapp.Components
+import org.junit.Test
abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val isRotated = testSpec.config.startRotation.isRotated()
protected val pipApp = PipAppHelper(instrumentation)
+ protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
-
// Helper class to process test actions by broadcast.
protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
private fun createIntentWithAction(broadcastAction: String): Intent {
@@ -121,13 +131,13 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
removeAllTasksButHome()
if (!eachRun) {
pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipWindowShown()
+ wmHelper.waitFor { it.wmState.hasPipWindow() }
}
}
eachRun {
if (eachRun) {
pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipWindowShown()
+ wmHelper.waitFor { it.wmState.hasPipWindow() }
}
}
}
@@ -148,4 +158,35 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
extraSpec(this, configuration)
}
}
+
+ @Presubmit
+ @Test
+ open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+
+ @Presubmit
+ @Test
+ open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+
+ @Presubmit
+ @Test
+ open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+
+ @Presubmit
+ @Test
+ open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+
+ @Presubmit
+ @Test
+ open fun navBarLayerRotatesAndScales() =
+ testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+
+ @Presubmit
+ @Test
+ open fun statusBarLayerRotatesScales() =
+ testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+
+ @Presubmit
+ @Test
+ open fun noUncoveredRegions() =
+ testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 7916ce59af52..9aab7f30e6f8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -25,10 +25,6 @@ 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.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
@@ -83,11 +79,19 @@ class SetRequestedOrientationWhilePinnedTest(
}
}
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
@Presubmit
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
- coversAtMost(startingBounds, pipApp.defaultWindowName)
+ frameRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
}
}
@@ -101,35 +105,25 @@ class SetRequestedOrientationWhilePinnedTest(
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
- coversAtMost(startingBounds, pipApp.defaultWindowName)
+ visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
}
}
@Presubmit
@Test
- fun pipAppLayerCoversFullScreen() {
- testSpec.assertLayersEnd {
- coversExactly(endingBounds, pipApp.defaultWindowName)
- }
+ fun pipAlwaysVisible() = testSpec.assertWm {
+ this.showsAppWindow(pipApp.windowName)
}
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
-
- @Presubmit
- @Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun pipAppLayerCoversFullScreen() {
+ testSpec.assertLayersEnd {
+ visibleRegion(pipApp.defaultWindowName).coversExactly(endingBounds)
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index bf84a6e30c98..da95c77d2b89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -16,8 +16,6 @@
package com.android.wm.shell;
-import android.os.Looper;
-
import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
@@ -40,11 +38,6 @@ public class TestShellExecutor implements ShellExecutor {
}
@Override
- public void removeAllCallbacks() {
- mRunnables.clear();
- }
-
- @Override
public void removeCallbacks(Runnable r) {
mRunnables.remove(r);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 4cedc483fc21..ef046d48e1cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.graphics.Insets;
import android.graphics.Point;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -119,7 +120,8 @@ public class DisplayImeControllerTest {
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ new InsetsSourceControl(
+ ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0), Insets.NONE)
};
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index c1c4c6dd08d7..2f2bbba11646 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -205,7 +205,7 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -217,12 +217,12 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -234,12 +234,12 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -251,7 +251,7 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -263,7 +263,7 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -276,13 +276,13 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -295,13 +295,13 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
index d6bcf0375f32..3f47c040dd8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
@@ -22,6 +22,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -35,6 +36,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import org.junit.Before;
import org.junit.Test;
@@ -48,7 +50,8 @@ import org.mockito.MockitoAnnotations;
public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
private DisplayAreaInfo mDisplayAreaInfo;
private Display mDisplay;
- private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
+ private DisplayLayout mDisplayLayout;
+ private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer;
private WindowContainerToken mToken;
private SurfaceControl mLeash;
private TestableLooper mTestableLooper;
@@ -65,37 +68,38 @@ public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
mToken = new WindowContainerToken(mMockRealToken);
mLeash = new SurfaceControl();
mDisplay = mContext.getDisplay();
+ mDisplayLayout = new DisplayLayout(mContext, mDisplay);
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
FEATURE_ONE_HANDED_BACKGROUND_PANEL);
- mBackgroundPanelOrganizer = new OneHandedBackgroundPanelOrganizer(mContext, mWindowManager,
- mMockDisplayController, Runnable::run);
+ mSpiedBackgroundPanelOrganizer = spy(
+ new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, Runnable::run));
}
@Test
public void testOnDisplayAreaAppeared() {
- mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+ mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mTestableLooper.processAllMessages();
- assertThat(mBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull();
+ assertThat(mSpiedBackgroundPanelOrganizer.getBackgroundSurface()).isNotNull();
}
@Test
public void testShowBackgroundLayer() {
- mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
- mBackgroundPanelOrganizer.showBackgroundPanelLayer();
+ mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+ mSpiedBackgroundPanelOrganizer.showBackgroundPanelLayer();
mTestableLooper.processAllMessages();
- assertThat(mBackgroundPanelOrganizer.mIsShowing).isTrue();
+ assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isTrue();
}
@Test
public void testRemoveBackgroundLayer() {
- mBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
- mBackgroundPanelOrganizer.removeBackgroundPanelLayer();
+ mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+ mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer();
mTestableLooper.processAllMessages();
- assertThat(mBackgroundPanelOrganizer.mIsShowing).isFalse();
+ assertThat(mSpiedBackgroundPanelOrganizer.mIsShowing).isFalse();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index c5221dee9216..e309f9659338 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -16,28 +16,32 @@
package com.android.wm.shell.onehanded;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.om.IOverlayManager;
+import android.graphics.Rect;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
+import android.view.Surface;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -50,7 +54,10 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedControllerTest extends OneHandedTestCase {
+ private int mCurrentUser = UserHandle.myUserId();
+
Display mDisplay;
+ DisplayLayout mDisplayLayout;
OneHandedController mSpiedOneHandedController;
OneHandedTimeoutHandler mSpiedTimeoutHandler;
@@ -67,6 +74,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Mock
OneHandedGestureHandler mMockGestureHandler;
@Mock
+ OneHandedSettingsUtil mMockSettingsUitl;
+ @Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
IOverlayManager mMockOverlayManager;
@@ -79,34 +88,43 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Mock
Handler mMockShellMainHandler;
- final boolean mDefaultEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- getTestContext().getContentResolver());
- final boolean mDefaultSwipeToNotificationEnabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- getTestContext().getContentResolver());
- final boolean mDefaultTapAppToExitEnabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
- getTestContext().getContentResolver());
+ final boolean mDefaultEnabled = true;
+ final boolean mDefaultSwipeToNotificationEnabled = false;
+ final boolean mDefaultTapAppToExitEnabled = true;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
+ mDisplayLayout = new DisplayLayout(mContext, mDisplay);
mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash);
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
+ mDefaultEnabled);
+ when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
+ OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
+ when(mMockSettingsUitl.getSettingsTapsAppToExit(any(), anyInt())).thenReturn(
+ mDefaultTapAppToExitEnabled);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
+ mDefaultSwipeToNotificationEnabled);
+
+ when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn(
+ new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height()));
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
- mWindowManager,
mMockDisplayController,
mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
mMockTutorialHandler,
mMockGestureHandler,
+ mMockSettingsUitl,
mSpiedTimeoutHandler,
mMockUiEventLogger,
mMockOverlayManager,
@@ -121,22 +139,13 @@ public class OneHandedControllerTest extends OneHandedTestCase {
final OneHandedAnimationController animationController = new OneHandedAnimationController(
mContext);
OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
- mContext, mWindowManager, mMockDisplayController, animationController,
- mMockTutorialHandler, mMockBackgroundOrganizer, mMockShellMainExecutor);
+ mContext, mDisplayLayout, animationController, mMockTutorialHandler,
+ mMockBackgroundOrganizer, mMockShellMainExecutor);
assertThat(displayAreaOrganizer.isInOneHanded()).isFalse();
}
@Test
- public void testNoRegisterAndUnregisterInSameCall() {
- if (mDefaultEnabled) {
- verify(mMockDisplayAreaOrganizer, never()).unregisterOrganizer();
- } else {
- verify(mMockDisplayAreaOrganizer, never()).registerOrganizer(FEATURE_ONE_HANDED);
- }
- }
-
- @Test
public void testStartOneHandedShouldTriggerScheduleOffset() {
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
mSpiedOneHandedController.setOneHandedEnabled(true);
@@ -190,35 +199,39 @@ public class OneHandedControllerTest extends OneHandedTestCase {
public void testUpdateEnabled() {
mSpiedOneHandedController.setOneHandedEnabled(true);
- verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled);
- verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(
- mDefaultEnabled || mDefaultSwipeToNotificationEnabled);
+ verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
+ verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
}
@Test
public void testUpdateSwipeToNotification() {
mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled);
- verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled);
- verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(
- mDefaultEnabled || mDefaultSwipeToNotificationEnabled);
+ verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
+ verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
}
@Test
- public void testSettingsObserverUpdateTapAppToExit() {
- mSpiedOneHandedController.onTaskChangeExitSettingChanged();
- if (mDefaultTapAppToExitEnabled) {
- verify(mMockTaskStackListener, atLeastOnce()).addListener(any());
- } else {
- verify(mMockTaskStackListener, atLeastOnce()).removeListener(any());
- }
+ public void testTapAppToExitEnabledAddListener() {
+ mSpiedOneHandedController.setTaskChangeToExit(mDefaultTapAppToExitEnabled);
+
+ // If device settings default ON, then addListener() will be trigger 1 time at init
+ verify(mMockTaskStackListener, atLeastOnce()).addListener(any());
+ }
+
+ @Test
+ public void testTapAppToExitDisabledRemoveListener() {
+ mSpiedOneHandedController.setTaskChangeToExit(!mDefaultTapAppToExitEnabled);
+
+ // If device settings default ON, then removeListener() will be trigger 1 time at init
+ verify(mMockTaskStackListener, atLeastOnce()).removeListener(any());
}
@Test
public void testSettingsObserverUpdateEnabled() {
mSpiedOneHandedController.onEnabledSettingChanged();
- verify(mSpiedOneHandedController).setOneHandedEnabled(mDefaultEnabled);
+ verify(mSpiedOneHandedController).setOneHandedEnabled(anyBoolean());
}
@Test
@@ -232,14 +245,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
public void testSettingsObserverUpdateSwipeToNotification() {
mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged();
- // Swipe to notification function is opposite with one handed mode function
- if (mDefaultSwipeToNotificationEnabled) {
- verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(
- mDefaultSwipeToNotificationEnabled);
- } else {
- verify(mSpiedOneHandedController, never()).setSwipeToNotificationEnabled(
- mDefaultSwipeToNotificationEnabled);
- }
+ verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean());
}
@Test
@@ -288,4 +294,67 @@ public class OneHandedControllerTest extends OneHandedTestCase {
verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
}
+
+ @Test
+ public void testRotation90CanNotStartOneHanded() {
+ final DisplayLayout landscapeDisplayLayout = new DisplayLayout(mDisplayLayout);
+ landscapeDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(landscapeDisplayLayout);
+ mSpiedOneHandedController.setOneHandedEnabled(true);
+ mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
+ mSpiedOneHandedController.startOneHanded();
+
+ verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testRotation180CanStartOneHanded() {
+ final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout);
+ testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout);
+ mSpiedOneHandedController.setOneHandedEnabled(true);
+ mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
+ mSpiedOneHandedController.startOneHanded();
+
+ verify(mMockDisplayAreaOrganizer).scheduleOffset(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testRotation270CanNotStartOneHanded() {
+ final DisplayLayout testDisplayLayout = new DisplayLayout(mDisplayLayout);
+ testDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(testDisplayLayout);
+ mSpiedOneHandedController.setOneHandedEnabled(true);
+ mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
+ mSpiedOneHandedController.startOneHanded();
+
+ verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testDisabled3ButtonGestureWhenKeyguardOn() {
+ final boolean isOneHandedEnabled = true;
+ final boolean isLockWhenKeyguardOn = true;
+ final boolean isEnabledWhenKeyguardOn = false;
+ mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
+ mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
+
+ verify(mMockGestureHandler).onGestureEnabled(isEnabledWhenKeyguardOn);
+ }
+
+ @Test
+ public void testEnabled3ButtonGestureWhenKeyguardGoingAway() {
+ final boolean isOneHandedEnabled = true;
+ final boolean isLockWhenKeyguardOn = false;
+ final boolean isEnabledWhenKeyguardOn = false;
+ mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
+ reset(mMockGestureHandler);
+
+ mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);
+
+ verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 1fa1e2ff69b6..f654bb59ef0c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -19,6 +19,9 @@ package com.android.wm.shell.onehanded;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
+import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
+import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -32,6 +35,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -47,6 +51,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before;
@@ -67,6 +72,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
DisplayAreaInfo mDisplayAreaInfo;
Display mDisplay;
+ DisplayLayout mDisplayLayout;
OneHandedDisplayAreaOrganizer mSpiedDisplayAreaOrganizer;
OneHandedTutorialHandler mTutorialHandler;
OneHandedAnimationController.OneHandedTransitionAnimator mFakeAnimator;
@@ -103,6 +109,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mToken = new WindowContainerToken(mMockRealToken);
mLeash = new SurfaceControl();
mDisplay = mContext.getDisplay();
+ mDisplayLayout = new DisplayLayout(mContext, mDisplay);
mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED);
mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
when(mMockAnimationController.getAnimator(any(), any(), any(), any())).thenReturn(null);
@@ -121,8 +128,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
when(mMockLeash.getHeight()).thenReturn(DISPLAY_HEIGHT);
mSpiedDisplayAreaOrganizer = spy(new OneHandedDisplayAreaOrganizer(mContext,
- mWindowManager,
- mMockDisplayController,
+ mDisplayLayout,
mMockAnimationController,
mTutorialHandler,
mMockBackgroundOrganizer,
@@ -173,8 +179,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -182,8 +190,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 270
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270,
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -191,8 +201,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 90
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -200,8 +214,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 270
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -209,8 +227,12 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 0
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -218,26 +240,38 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 180
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
- public void testRotation_Seascape_270_to_portrait_0() {
+ public void testRotation_seascape_270_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 0
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
- public void testRotation_seascape_90_to_portrait_180() {
+ public void testRotation_seascape_270_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 180
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@@ -245,7 +279,10 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 0
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0,
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
+ mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -254,16 +291,23 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_0_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 180
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180,
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
mMockWindowContainerTransaction);
- verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+ verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_portrait_180_to_portrait_180() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 180
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
+ mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -272,16 +316,25 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_portrait_180_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 0
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
mMockWindowContainerTransaction);
- verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+ verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_landscape_90_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 90
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@@ -290,26 +343,60 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
public void testRotation_landscape_90_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 270
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
mMockWindowContainerTransaction);
- verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+ verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
}
@Test
public void testRotation_seascape_270_to_seascape_270() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 270
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
+ mMockWindowContainerTransaction);
+
+ verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
mMockWindowContainerTransaction);
verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
}
@Test
- public void testRotation_seascape_90_to_landscape_90() {
+ public void testRotation_seascape_270_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 90
- mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90,
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout);
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
mMockWindowContainerTransaction);
- verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
+
+ verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(
+ mMockWindowContainerTransaction);
+ verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testTriggerOffset() {
+ final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds();
+ final int offset = 100;
+ testBounds.offsetTo(0, offset);
+ mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER);
+
+ assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds);
+ }
+
+ @Test
+ public void testExitOffsetToZero() {
+ final Rect testBounds = mSpiedDisplayAreaOrganizer.getLastDisplayBounds();
+ final int offset = 100;
+ mSpiedDisplayAreaOrganizer.finishOffset(offset, TRANSITION_DIRECTION_TRIGGER);
+ mSpiedDisplayAreaOrganizer.finishOffset(0, TRANSITION_DIRECTION_EXIT);
+
+ assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
index f683e4af41bd..5d82a700545c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedGestureHandlerTest.java
@@ -16,21 +16,19 @@
package com.android.wm.shell.onehanded;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.view.Surface;
import android.view.ViewConfiguration;
-import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -44,24 +42,20 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class OneHandedGestureHandlerTest extends OneHandedTestCase {
OneHandedGestureHandler mGestureHandler;
- @Mock
- DisplayController mMockDisplayController;
+ DisplayLayout mDisplayLayout;
@Mock
DisplayLayout mMockDisplayLayout;
@Mock
ShellExecutor mMockShellMainExecutor;
- @Mock
- WindowContainerTransaction mMockWct;
@Before
public void setUp() {
final int mockNavBarHeight = 100;
MockitoAnnotations.initMocks(this);
- mGestureHandler = new OneHandedGestureHandler(mContext, mWindowManager,
- mMockDisplayController, ViewConfiguration.get(mTestContext),
- mMockShellMainExecutor);
+ mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
+ mGestureHandler = new OneHandedGestureHandler(mContext, mDisplayLayout,
+ ViewConfiguration.get(mTestContext), mMockShellMainExecutor);
when(mMockDisplayLayout.navBarFrameHeight()).thenReturn(mockNavBarHeight);
- when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(mMockDisplayLayout);
}
@Test
@@ -81,7 +75,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testOneHandedDisabled_shouldDisposeInputChannel() {
- mGestureHandler.onOneHandedEnabled(false);
+ mGestureHandler.onGestureEnabled(false);
assertThat(mGestureHandler.mInputMonitor).isNull();
assertThat(mGestureHandler.mInputEventReceiver).isNull();
@@ -89,7 +83,7 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testChangeNavBarToNon3Button_shouldDisposeInputChannel() {
- mGestureHandler.onOneHandedEnabled(true);
+ mGestureHandler.onGestureEnabled(true);
mGestureHandler.onThreeButtonModeEnabled(false);
assertThat(mGestureHandler.mInputMonitor).isNull();
@@ -98,11 +92,38 @@ public class OneHandedGestureHandlerTest extends OneHandedTestCase {
@Test
public void testOnlyHandleGestureInPortraitMode() {
- mGestureHandler.onOneHandedEnabled(true);
- mGestureHandler.onRotateDisplay(DEFAULT_DISPLAY, Surface.ROTATION_0, Surface.ROTATION_90,
- mMockWct);
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mGestureHandler.onGestureEnabled(true);
+ mGestureHandler.onRotateDisplay(mDisplayLayout);
assertThat(mGestureHandler.mInputMonitor).isNull();
assertThat(mGestureHandler.mInputEventReceiver).isNull();
}
+
+ @Test
+ public void testRotation90ShouldNotRegisterEventReceiver() throws InterruptedException {
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mGestureHandler.onGestureEnabled(true);
+ mGestureHandler.onRotateDisplay(mDisplayLayout);
+
+ verify(mMockShellMainExecutor, never()).executeBlocking(any());
+ }
+
+ @Test
+ public void testRotation180ShouldNotRegisterEventReceiver() throws InterruptedException {
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mGestureHandler.onGestureEnabled(true);
+ mGestureHandler.onRotateDisplay(mDisplayLayout);
+
+ verify(mMockShellMainExecutor, never()).executeBlocking(any());
+ }
+
+ @Test
+ public void testRotation270ShouldNotRegisterEventReceiver() throws InterruptedException {
+ mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mGestureHandler.onGestureEnabled(true);
+ mGestureHandler.onRotateDisplay(mDisplayLayout);
+
+ verify(mMockShellMainExecutor, never()).executeBlocking(any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
index 61643d86c8d9..1e6c41af4397 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
@@ -16,17 +16,11 @@
package com.android.wm.shell.onehanded;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS;
-
-import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
import android.content.ContentResolver;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -34,76 +28,30 @@ import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedSettingsUtilTest extends OneHandedTestCase {
- ContentResolver mContentResolver;
- ContentObserver mContentObserver;
- boolean mOnChanged;
+ OneHandedSettingsUtil mSettingsUtil;
+
+ @Mock
+ ContentResolver mMockContentResolver;
+ @Mock
+ ContentObserver mMockContentObserver;
@Before
public void setUp() {
- mContentResolver = mContext.getContentResolver();
- mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- mOnChanged = true;
- }
- };
- }
-
- @Test
- public void testRegisterSecureKeyObserver() {
- final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
+ MockitoAnnotations.initMocks(this);
- assertThat(result).isNotNull();
-
- OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
+ mSettingsUtil = new OneHandedSettingsUtil();
}
@Test
public void testUnregisterSecureKeyObserver() {
- OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
- OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
-
- assertThat(mOnChanged).isFalse();
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.TAPS_APP_TO_EXIT, 0);
-
- assertThat(mOnChanged).isFalse();
- }
+ mSettingsUtil.unregisterSettingsKeyObserver(mMockContentResolver, mMockContentObserver);
- @Test
- public void testGetSettingsIsOneHandedModeEnabled() {
- assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContentResolver)).isAnyOf(true, false);
- }
-
- @Test
- public void testGetSettingsTapsAppToExit() {
- assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit(
- mContentResolver)).isAnyOf(true, false);
- }
-
- @Test
- public void testGetSettingsOneHandedModeTimeout() {
- assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
- mContentResolver)).isAnyOf(
- ONE_HANDED_TIMEOUT_NEVER,
- ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS,
- ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS,
- ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
- }
-
- @Test
- public void testGetSettingsSwipeToNotificationEnabled() {
- assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContentResolver)).isAnyOf(true, false);
+ verify(mMockContentResolver).unregisterContentObserver(any());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index 69c537c2efbe..2886bb1e905a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -64,6 +64,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
Handler mMockShellMainHandler;
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
+ @Mock
+ OneHandedSettingsUtil mMockSettingsUtil;
@Before
public void setUp() {
@@ -73,13 +75,13 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase {
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
mOneHandedController = new OneHandedController(
mContext,
- mWindowManager,
mMockDisplayController,
mMockBackgroundOrganizer,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
mMockTutorialHandler,
mMockGestureHandler,
+ mMockSettingsUtil,
mTimeoutHandler,
mMockUiEventLogger,
mMockOverlayManager,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f2b4e9761226..700bf7850604 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -42,6 +42,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -54,6 +55,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Unit tests for {@link PipController}
*/
@@ -75,6 +78,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
+ @Mock private Optional<OneHandedController> mMockOneHandedController;
@Before
public void setUp() throws RemoteException {
@@ -83,7 +87,7 @@ public class PipControllerTest extends ShellTestCase {
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockExecutor);
+ mMockTaskStackListener, mMockOneHandedController, mMockExecutor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -116,7 +120,7 @@ public class PipControllerTest extends ShellTestCase {
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockExecutor));
+ mMockTaskStackListener, mMockOneHandedController, mMockExecutor));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 207db9e80511..78b3d4e3ca8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -83,12 +83,11 @@ public class StartingSurfaceDrawerTests {
}
@Override
- protected boolean postAddWindow(int taskId, IBinder appToken,
+ protected void postAddWindow(int taskId, IBinder appToken,
View view, WindowManager wm, WindowManager.LayoutParams params) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
- return true;
}
@Override
@@ -113,7 +112,8 @@ public class StartingSurfaceDrawerTests {
spyOn(context);
spyOn(realWindowManager);
try {
- doReturn(context).when(context).createPackageContext(anyString(), anyInt());
+ doReturn(context).when(context)
+ .createPackageContextAsUser(anyString(), anyInt(), any());
} catch (PackageManager.NameNotFoundException e) {
//
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 926108c41e5e..c1733de75535 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -30,6 +31,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -295,6 +298,44 @@ public class ShellTransitionTests {
verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
}
+ @Test
+ public void testOneShotRemoteHandler() {
+ Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
+ mMainExecutor, mAnimExecutor);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ final boolean[] remoteCalled = new boolean[]{false};
+ final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction();
+ IRemoteTransition testRemote = new IRemoteTransition.Stub() {
+ @Override
+ public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ remoteCalled[0] = true;
+ finishCallback.onTransitionFinished(remoteFinishWCT);
+ }
+ };
+
+ final int transitType = TRANSIT_FIRST_CUSTOM + 1;
+
+ OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, testRemote);
+ // Verify that it responds to the remote but not other things.
+ IBinder transitToken = new Binder();
+ assertNotNull(oneShot.handleRequest(transitToken,
+ new TransitionRequestInfo(transitType, null, testRemote)));
+ assertNull(oneShot.handleRequest(transitToken,
+ new TransitionRequestInfo(transitType, null, null)));
+
+ Transitions.TransitionFinishCallback testFinish =
+ mock(Transitions.TransitionFinishCallback.class);
+ // Verify that it responds to animation properly
+ oneShot.setTransition(transitToken);
+ IBinder anotherToken = new Binder();
+ assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
+ mock(SurfaceControl.Transaction.class), testFinish));
+ assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
+ mock(SurfaceControl.Transaction.class), testFinish));
+ }
+
class TransitionInfoBuilder {
final TransitionInfo mInfo;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index c0ef7be8b673..7e45f952d389 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -226,8 +226,6 @@ void AssetManager2::BuildDynamicRefTable() {
}
void AssetManager2::DumpToLog() const {
- base::ScopedLogSeverity _log(base::INFO);
-
LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this);
std::string list;
@@ -1721,7 +1719,6 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& o) {
}
void Theme::Dump() const {
- base::ScopedLogSeverity _log(base::INFO);
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
for (int p = 0; p < packages_.size(); p++) {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 168a863df2bc..1e90b7c71376 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1379,6 +1379,11 @@ struct ResTable_typeSpec
enum : uint32_t {
// Additional flag indicating an entry is public.
SPEC_PUBLIC = 0x40000000u,
+
+ // Additional flag indicating the resource id for this resource may change in a future
+ // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be
+ // public to be exposed as an API to other applications.
+ SPEC_STAGED_API = 0x20000000u,
};
};
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 971a53a8b2dc..e58f31fd15eb 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -126,7 +126,7 @@ bool Properties::load() {
SkAndroidFrameworkTraceUtil::setEnableTracing(
base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
- runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
+ runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index dcb79babad24..ea9cbd592d29 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -160,7 +160,13 @@ enum DebugLevel {
/**
* Property for whether this is running in the emulator.
*/
-#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
+#define PROPERTY_IS_EMULATOR "ro.boot.qemu"
+
+/**
+ * Turns on the Skia GPU option "reduceOpsTaskSplitting" which improves GPU
+ * efficiency but may increase VRAM consumption. Default is "false".
+ */
+#define PROPERTY_REDUCE_OPS_TASK_SPLITTING "renderthread.skia.reduceopstasksplitting"
///////////////////////////////////////////////////////////////////////////////
// Misc
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index b71bb07dbc86..145526996678 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -120,12 +120,6 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
- if (bitmap->colorType() == kRGBA_F16_SkColorType &&
- !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) {
- ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
- return CopyResult::DestinationInvalid;
- }
-
CopyResult copyResult = CopyResult::UnknownError;
int displayedWidth = imgWidth, displayedHeight = imgHeight;
@@ -159,12 +153,10 @@ CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTran
bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
SkBitmap* bitmap) {
- /* This intermediate surface is present to work around a bug in SwiftShader that
- * prevents us from reading the contents of the layer's texture directly. The
- * workaround involves first rendering that texture into an intermediate buffer and
- * then reading from the intermediate buffer into the bitmap.
- * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733
- * with reading incorrect data from EGLImage backed SkImage (likely a driver bug).
+ /* This intermediate surface is present to work around limitations that LayerDrawable expects
+ * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around
+ * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
+ * software buffer.
*/
sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
SkBudgeted::kYes, bitmap->info(), 0,
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index d4fd1053b17f..0804e0aec278 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -33,7 +33,8 @@ static const SkString stretchShader = SkString(R"(
uniform float uMaxStretchIntensity;
// Maximum percentage to stretch beyond bounds of target
- uniform float uStretchAffectedDist;
+ uniform float uStretchAffectedDistX;
+ uniform float uStretchAffectedDistY;
// Distance stretched as a function of the normalized overscroll times
// scale intensity
@@ -57,8 +58,7 @@ static const SkString stretchShader = SkString(R"(
uniform float viewportWidth; // target height in pixels
uniform float viewportHeight; // target width in pixels
- void computeOverscrollStart(
- out float outPos,
+ float computeOverscrollStart(
float inPos,
float overscroll,
float uStretchAffectedDist,
@@ -67,11 +67,10 @@ static const SkString stretchShader = SkString(R"(
float offsetPos = uStretchAffectedDist - inPos;
float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos);
float stretchIntensity = overscroll * posBasedVariation;
- outPos = distanceStretched - (offsetPos / (1. + stretchIntensity));
+ return distanceStretched - (offsetPos / (1. + stretchIntensity));
}
- void computeOverscrollEnd(
- out float outPos,
+ float computeOverscrollEnd(
float inPos,
float overscroll,
float reverseStretchDist,
@@ -81,21 +80,23 @@ static const SkString stretchShader = SkString(R"(
float offsetPos = inPos - reverseStretchDist;
float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos));
float stretchIntensity = (-overscroll) * posBasedVariation;
- outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
+ return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
}
- void computeOverscroll(
- out float outPos,
+ // Prefer usage of return values over out parameters as it enables
+ // SKSL to properly inline method calls and works around potential GPU
+ // driver issues on Wembly. See b/182566543 for details
+ float computeOverscroll(
float inPos,
float overscroll,
float uStretchAffectedDist,
float distanceStretched,
float distanceDiff
) {
- if (overscroll > 0) {
+ float outPos = inPos;
+ if (overscroll > 0) {
if (inPos <= uStretchAffectedDist) {
- computeOverscrollStart(
- outPos,
+ outPos = computeOverscrollStart(
inPos,
overscroll,
uStretchAffectedDist,
@@ -108,8 +109,7 @@ static const SkString stretchShader = SkString(R"(
if (overscroll < 0) {
float stretchAffectedDist = 1. - uStretchAffectedDist;
if (inPos >= stretchAffectedDist) {
- computeOverscrollEnd(
- outPos,
+ outPos = computeOverscrollEnd(
inPos,
overscroll,
stretchAffectedDist,
@@ -120,6 +120,7 @@ static const SkString stretchShader = SkString(R"(
outPos = -distanceDiff + inPos;
}
}
+ return outPos;
}
vec4 main(vec2 coord) {
@@ -134,19 +135,17 @@ static const SkString stretchShader = SkString(R"(
inV += uScrollY;
outU = inU;
outV = inV;
- computeOverscroll(
- outU,
+ outU = computeOverscroll(
inU,
uOverscrollX,
- uStretchAffectedDist,
+ uStretchAffectedDistX,
uDistanceStretchedX,
uDistDiffX
);
- computeOverscroll(
- outV,
+ outV = computeOverscroll(
inV,
uOverscrollY,
- uStretchAffectedDist,
+ uStretchAffectedDistY,
uDistanceStretchedY,
uDistDiffY
);
@@ -166,16 +165,14 @@ sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapsho
return mStretchFilter;
}
- float distanceNotStretchedX = maxStretchAmount / stretchArea.width();
- float distanceNotStretchedY = maxStretchAmount / stretchArea.height();
- float normOverScrollDistX = mStretchDirection.x();
- float normOverScrollDistY = mStretchDirection.y();
- float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX));
- float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY));
- float diffX = distanceStretchedX - distanceNotStretchedX;
- float diffY = distanceStretchedY - distanceNotStretchedY;
float viewportWidth = stretchArea.width();
float viewportHeight = stretchArea.height();
+ float normOverScrollDistX = mStretchDirection.x();
+ float normOverScrollDistY = mStretchDirection.y();
+ float distanceStretchedX = maxStretchAmountX / (1 + abs(normOverScrollDistX));
+ float distanceStretchedY = maxStretchAmountY / (1 + abs(normOverScrollDistY));
+ float diffX = distanceStretchedX;
+ float diffY = distanceStretchedY;
if (mBuilder == nullptr) {
mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
@@ -183,7 +180,8 @@ sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapsho
mBuilder->child("uContentTexture") = snapshotImage->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
- mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1);
+ mBuilder->uniform("uStretchAffectedDistX").set(&maxStretchAmountX, 1);
+ mBuilder->uniform("uStretchAffectedDistY").set(&maxStretchAmountY, 1);
mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
mBuilder->uniform("uDistDiffX").set(&diffX, 1);
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index d2da06b31f68..8221b41ff4e5 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -33,8 +33,12 @@ public:
SmoothStep,
};
- StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount)
- : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {}
+ StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmountX,
+ float maxStretchAmountY)
+ : stretchArea(area)
+ , maxStretchAmountX(maxStretchAmountX)
+ , maxStretchAmountY(maxStretchAmountY)
+ , mStretchDirection(direction) {}
StretchEffect() {}
@@ -50,7 +54,8 @@ public:
this->stretchArea = other.stretchArea;
this->mStretchDirection = other.mStretchDirection;
this->mStretchFilter = nullptr;
- this->maxStretchAmount = other.maxStretchAmount;
+ this->maxStretchAmountX = other.maxStretchAmountX;
+ this->maxStretchAmountY = other.maxStretchAmountY;
return *this;
}
@@ -67,13 +72,15 @@ public:
return setEmpty();
}
stretchArea.join(other.stretchArea);
- maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+ maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX);
+ maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY);
}
sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const;
SkRect stretchArea {0, 0, 0, 0};
- float maxStretchAmount = 0;
+ float maxStretchAmountX = 0;
+ float maxStretchAmountY = 0;
void setStretchDirection(const SkVector& direction) {
mStretchFilter = nullptr;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index ade63e5b832c..5d9fad5b676e 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -45,7 +45,8 @@ sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
return SkColorSpace::MakeSRGB();
}
-ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
+ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker,
+ SkCodec::ZeroInitialized zeroInit)
: mCodec(std::move(codec))
, mPeeker(std::move(peeker))
, mDecodeSize(mCodec->codec()->dimensions())
@@ -57,6 +58,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu
mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() }
: mDecodeSize;
this->rewind();
+ mOptions.fZeroInitialized = zeroInit;
}
ImageDecoder::~ImageDecoder() = default;
@@ -446,10 +448,17 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
ALOGE("Failed to invert matrix!");
}
}
+
+ // Even if the client did not provide zero initialized memory, the
+ // memory we decode into is.
+ mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
}
auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+ // The next call to decode() may not provide zero initialized memory.
+ mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
+
if (scale || handleOrigin || mCropRect) {
SkBitmap scaledBm;
if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index cbfffd5e9291..cef2233fc371 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -34,8 +34,8 @@ public:
std::unique_ptr<SkAndroidCodec> mCodec;
sk_sp<SkPngChunkReader> mPeeker;
- ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
- sk_sp<SkPngChunkReader> peeker = nullptr);
+ ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr,
+ SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized);
~ImageDecoder();
SkISize getSampledDimensions(int sampleSize) const;
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index ad7741b61e9f..f7b8c014be6e 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -141,7 +141,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream,
}
const bool isNinePatch = peeker->mPatch != nullptr;
- ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker));
+ ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker),
+ SkCodec::kYes_ZeroInitialized);
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(),
animated, isNinePatch);
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index fc7d0d181949..fffa80614370 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -181,9 +181,10 @@ static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA j
static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
jfloat left, jfloat top, jfloat right,
- jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
- StretchEffect effect =
- StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max);
+ jfloat bottom, jfloat vX, jfloat vY, jfloat maxX,
+ jfloat maxY) {
+ StretchEffect effect = StretchEffect(SkRect::MakeLTRB(left, top, right, bottom),
+ {.fX = vX, .fY = vY}, maxX, maxY);
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
effect);
@@ -662,7 +663,7 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
info.canvasContext.getFrameNumber(), area.left, area.top,
area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
- effect->maxStretchAmount);
+ effect->maxStretchAmountX, effect->maxStretchAmountY);
#endif
env->DeleteLocalRef(localref);
}
@@ -738,7 +739,7 @@ static const JNINativeMethod gMethods[] = {
{"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty},
{"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone},
{"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch},
- {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
+ {"nStretch", "(JFFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
{"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow},
{"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor},
{"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor},
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 5dc02e8454ac..adf4aee8b931 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -20,6 +20,7 @@
#include "CanvasContext.h"
#include "DeviceInfo.h"
#include "EglManager.h"
+#include "Properties.h"
#include "Readback.h"
#include "RenderProxy.h"
#include "VulkanManager.h"
@@ -40,6 +41,7 @@
#include <utils/Mutex.h>
#include <thread>
+#include <android-base/properties.h>
#include <ui/FatVector.h>
namespace android {
@@ -251,6 +253,11 @@ void RenderThread::requireVkContext() {
void RenderThread::initGrContextOptions(GrContextOptions& options) {
options.fPreferExternalImagesOverES3 = true;
options.fDisableDistanceFieldPaths = true;
+ if (android::base::GetBoolProperty(PROPERTY_REDUCE_OPS_TASK_SPLITTING, false)) {
+ options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kYes;
+ } else {
+ options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;
+ }
}
void RenderThread::destroyRenderingContext() {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e93824dfbd30..01126860b3ba 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -336,6 +336,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
GET_DEV_PROC(ResetCommandBuffer);
GET_DEV_PROC(ResetFences);
GET_DEV_PROC(WaitForFences);
+ GET_DEV_PROC(FrameBoundaryANDROID);
}
void VulkanManager::initialize() {
@@ -516,6 +517,25 @@ void VulkanManager::finishFrame(SkSurface* surface) {
if (semaphore != VK_NULL_HANDLE) {
if (submitted == GrSemaphoresSubmitted::kYes) {
mSwapSemaphore = semaphore;
+ if (mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget =
+ surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (backendRenderTarget.getVkImageInfo(&info)) {
+ image = info.fImage;
+ } else {
+ ALOGE("Frame boundary: backend is not vulkan");
+ }
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
+ }
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
+ }
} else {
destroy_semaphore(mDestroySemaphoreContext);
mDestroySemaphoreContext = nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 0912369b611d..7b5fe19c64f5 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -31,6 +31,21 @@
#include <vk/GrVkExtensions.h>
#include <vulkan/vulkan.h>
+// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
+// (https://github.com/google/agi) to enable profiling of apps rendering via
+// HWUI. This extension is not defined in Khronos, hence the need to declare it
+// manually here. There's a superseding extension (VK_EXT_frame_boundary) being
+// discussed in Khronos, but in the meantime we use the bespoke
+// VK_ANDROID_frame_boundary. This is a device extension that is implemented by
+// AGI's Vulkan capture layer, such that it is only supported by devices when
+// AGI is doing a capture of the app.
+//
+// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has
+// landed in the spec.
+typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore,
+ VkImage image);
+#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary"
+
#include "Frame.h"
#include "IRenderPipeline.h"
#include "VulkanSurface.h"
@@ -160,6 +175,7 @@ private:
VkPtr<PFN_vkDestroyFence> mDestroyFence;
VkPtr<PFN_vkWaitForFences> mWaitForFences;
VkPtr<PFN_vkResetFences> mResetFences;
+ VkPtr<PFN_vkFrameBoundaryANDROID> mFrameBoundaryANDROID;
VkInstance mInstance = VK_NULL_HANDLE;
VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 1a3dbe7faaf6..ab23448ab93f 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -33,7 +33,7 @@ namespace {
constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
-constexpr char kRegularFont[] = "/system/fonts/NotoSerif.ttf";
+constexpr char kRegularFont[] = "/system/fonts/NotoSerif-Regular.ttf";
constexpr char kBoldFont[] = "/system/fonts/NotoSerif-Bold.ttf";
constexpr char kItalicFont[] = "/system/fonts/NotoSerif-Italic.ttf";
constexpr char kBoldItalicFont[] = "/system/fonts/NotoSerif-BoldItalic.ttf";