summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/OWNERS2
-rw-r--r--libs/WindowManager/Shell/Android.bp4
-rw-r--r--libs/WindowManager/Shell/res/layout/split_divider.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java125
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java240
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java171
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java205
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java67
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java)72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java)5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java14
-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.java123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java239
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java248
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java189
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java126
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java138
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt118
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt4
-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/helpers/SplitScreenHelper.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt46
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt19
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt118
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt159
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt85
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt203
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt127
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt127
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt135
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt135
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt96
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ResizeSplitScreenTest.kt208
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt95
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenTestBase.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenToLauncherTest.kt134
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java82
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java133
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java10
-rw-r--r--libs/hwui/Android.bp4
-rw-r--r--libs/hwui/DisplayList.h5
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/FrameInfo.cpp6
-rw-r--r--libs/hwui/FrameInfo.h5
-rw-r--r--libs/hwui/Layer.cpp86
-rw-r--r--libs/hwui/Layer.h3
-rw-r--r--libs/hwui/RecordingCanvas.cpp28
-rw-r--r--libs/hwui/RecordingCanvas.h6
-rw-r--r--libs/hwui/RenderNode.cpp2
-rw-r--r--libs/hwui/SaveFlags.h36
-rw-r--r--libs/hwui/VectorDrawable.cpp6
-rw-r--r--libs/hwui/WebViewFunctorManager.cpp63
-rw-r--r--libs/hwui/WebViewFunctorManager.h6
-rw-r--r--libs/hwui/apex/jni_runtime.cpp2
-rw-r--r--libs/hwui/canvas/CanvasFrontend.cpp124
-rw-r--r--libs/hwui/canvas/CanvasFrontend.h212
-rw-r--r--libs/hwui/canvas/CanvasOpRasterizer.cpp6
-rw-r--r--libs/hwui/canvas/CanvasOpRecorder.cpp22
-rw-r--r--libs/hwui/canvas/CanvasOpRecorder.h38
-rw-r--r--libs/hwui/canvas/CanvasOpTypes.h3
-rw-r--r--libs/hwui/canvas/CanvasOps.h45
-rw-r--r--libs/hwui/canvas/OpBuffer.h3
-rw-r--r--libs/hwui/canvas/Points.h48
-rw-r--r--libs/hwui/hwui/Canvas.h30
-rw-r--r--libs/hwui/jni/fonts/Font.cpp35
-rw-r--r--libs/hwui/jni/fonts/NativeFont.cpp125
-rw-r--r--libs/hwui/jni/pdf/PdfEditor.cpp4
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.h2
-rw-r--r--libs/hwui/private/hwui/WebViewFunctor.h45
-rw-r--r--libs/hwui/tests/common/TestUtils.h3
-rw-r--r--libs/hwui/tests/microbench/CanvasOpBench.cpp96
-rw-r--r--libs/hwui/tests/microbench/RenderNodeBench.cpp26
-rw-r--r--libs/hwui/tests/unit/CanvasFrontendTests.cpp213
-rw-r--r--libs/hwui/tests/unit/CanvasOpTests.cpp97
-rw-r--r--libs/hwui/tests/unit/CommonPoolTests.cpp4
-rw-r--r--libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp2
-rw-r--r--libs/hwui/tests/unit/RenderNodeDrawableTests.cpp16
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp2
-rw-r--r--libs/hwui/tests/unit/SkiaDisplayListTests.cpp12
-rw-r--r--libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp4
-rw-r--r--libs/incident/OWNERS1
-rw-r--r--libs/input/OWNERS1
-rw-r--r--libs/storage/OWNERS1
-rw-r--r--libs/usb/OWNERS1
169 files changed, 6146 insertions, 2077 deletions
diff --git a/libs/WindowManager/OWNERS b/libs/WindowManager/OWNERS
index 063d4594f2fa..2c61df96eb03 100644
--- a/libs/WindowManager/OWNERS
+++ b/libs/WindowManager/OWNERS
@@ -1,3 +1,3 @@
set noparent
-include ../../services/core/java/com/android/server/wm/OWNERS \ No newline at end of file
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 39e32c694d2e..96e0559b0df6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -122,12 +122,14 @@ android_library {
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"iconloader_base",
+ "jsr330",
"protolog-lib",
"SettingsLib",
"WindowManager-Shell-proto",
+ "jsr330"
],
kotlincflags: ["-Xjvm-default=enable"],
manifest: "AndroidManifest.xml",
min_sdk_version: "26",
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index b86f36a14d17..341fe617b2d0 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.wm.shell.apppairs.DividerView
+<com.android.wm.shell.common.split.DividerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
@@ -24,4 +24,4 @@
android:id="@+id/docked_divider_background"
android:background="@color/docked_divider_background"/>
-</com.android.wm.shell.apppairs.DividerView>
+</com.android.wm.shell.common.split.DividerView>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index ada7e1a9e3ab..45948dd9e800 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -19,6 +19,7 @@ package com.android.wm.shell;
import android.view.Gravity;
import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.letterbox.LetterboxConfigController;
import com.android.wm.shell.onehanded.OneHanded;
@@ -61,6 +62,7 @@ public final class ShellCommandHandler {
}
/** Dumps WM Shell internal state. */
+ @ExternalThread
public void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
pw.println();
@@ -76,6 +78,7 @@ public final class ShellCommandHandler {
/** Returns {@code true} if command was found and executed. */
+ @ExternalThread
public boolean handleCommand(String[] args, PrintWriter pw) {
if (args.length < 2) {
// Argument at position 0 is "WMShell".
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
index 118f189866eb..6c08079e586e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERB
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.letterbox.LetterboxTaskListener;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -39,6 +40,7 @@ public class ShellInit {
private final Optional<AppPairs> mAppPairsOptional;
private final LetterboxTaskListener mLetterboxTaskListener;
private final FullscreenTaskListener mFullscreenTaskListener;
+ private final Transitions mTransitions;
public ShellInit(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
@@ -46,7 +48,8 @@ public class ShellInit {
Optional<SplitScreen> splitScreenOptional,
Optional<AppPairs> appPairsOptional,
LetterboxTaskListener letterboxTaskListener,
- FullscreenTaskListener fullscreenTaskListener) {
+ FullscreenTaskListener fullscreenTaskListener,
+ Transitions transitions) {
mDisplayImeController = displayImeController;
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
@@ -54,8 +57,10 @@ public class ShellInit {
mAppPairsOptional = appPairsOptional;
mLetterboxTaskListener = letterboxTaskListener;
mFullscreenTaskListener = fullscreenTaskListener;
+ mTransitions = transitions;
}
+ @ExternalThread
public void init() {
// Start listening for display changes
mDisplayImeController.startMonitorDisplays();
@@ -70,5 +75,9 @@ public class ShellInit {
mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
// Bind the splitscreen impl to the drag drop controller
mDragAndDropController.setSplitScreenController(mSplitScreenOptional);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.register(mShellTaskOrganizer);
+ }
}
}
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 beb9690b5cbc..62d265aab38f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -43,8 +43,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -102,24 +100,17 @@ public class ShellTaskOrganizer extends TaskOrganizer {
/** @see #setPendingLaunchCookieListener */
private final ArrayMap<IBinder, TaskListener> mLaunchCookieToListener = new ArrayMap<>();
- // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks
- private final Transitions mTransitions;
-
private final Object mLock = new Object();
private final StartingSurfaceDrawer mStartingSurfaceDrawer;
- public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool,
- ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
- this(null, syncQueue, transactionPool, mainExecutor, animExecutor, context);
+ public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
+ this(null, mainExecutor, context);
}
@VisibleForTesting
- ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
- SyncTransactionQueue syncQueue, TransactionPool transactionPool,
- ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
+ ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
+ Context context) {
super(taskOrganizerController, mainExecutor);
- mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
// TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
// by a controller, that class should be create while porting
// ActivityRecord#addStartingWindow to WMShell.
@@ -272,6 +263,12 @@ public class ShellTaskOrganizer extends TaskOrganizer {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
+ if (data == null) {
+ // TODO(b/171749427): It means onTaskInfoChanged send before onTaskAppeared or
+ // after onTaskVanished, it should be fixed in controller side.
+ return;
+ }
+
final TaskListener oldListener = getTaskListener(data.getTaskInfo());
final TaskListener newListener = getTaskListener(taskInfo);
mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
index 388eb28223dc..a779531f8b91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -16,16 +16,16 @@
package com.android.wm.shell;
-import static android.window.TransitionInfo.TRANSIT_CLOSE;
-import static android.window.TransitionInfo.TRANSIT_HIDE;
-import static android.window.TransitionInfo.TRANSIT_OPEN;
-import static android.window.TransitionInfo.TRANSIT_SHOW;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.animation.Animator;
import android.animation.ValueAnimator;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
@@ -35,6 +35,8 @@ import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
import android.window.WindowOrganizer;
+import androidx.annotation.BinderThread;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -43,7 +45,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
/** Plays transition animations */
-public class Transitions extends ITransitionPlayer.Stub {
+public class Transitions {
private static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
@@ -54,16 +56,22 @@ public class Transitions extends ITransitionPlayer.Stub {
private final TransactionPool mTransactionPool;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
+ private final TransitionPlayerImpl mPlayerImpl;
/** Keeps track of currently tracked transitions and all the animations associated with each */
private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
- Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
+ public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mTransactionPool = pool;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mPlayerImpl = new TransitionPlayerImpl();
+ }
+
+ public void register(ShellTaskOrganizer taskOrganizer) {
+ taskOrganizer.registerTransitionPlayer(mPlayerImpl);
}
// TODO(shell-transitions): real animations
@@ -110,51 +118,78 @@ public class Transitions extends ITransitionPlayer.Stub {
}
private static boolean isOpeningType(@WindowManager.TransitionType int type) {
- return type == WindowManager.TRANSIT_OPEN
- || type == WindowManager.TRANSIT_TO_FRONT
+ return type == TRANSIT_OPEN
+ || type == TRANSIT_TO_FRONT
|| type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
}
- @Override
- public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info,
+ private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
// start task
- mMainExecutor.execute(() -> {
- if (!mActiveTransitions.containsKey(transitionToken)) {
- Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
- + " expecting one of " + mActiveTransitions.keySet());
- }
- if (mActiveTransitions.get(transitionToken) != null) {
- throw new IllegalStateException("Got a duplicate onTransitionReady call for "
- + transitionToken);
- }
- mActiveTransitions.put(transitionToken, new ArrayList<>());
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final SurfaceControl leash = info.getChanges().get(i).getLeash();
- final int mode = info.getChanges().get(i).getMode();
- if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
+ if (!mActiveTransitions.containsKey(transitionToken)) {
+ Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
+ + " expecting one of " + mActiveTransitions.keySet());
+ }
+ if (mActiveTransitions.get(transitionToken) != null) {
+ throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+ + transitionToken);
+ }
+ mActiveTransitions.put(transitionToken, new ArrayList<>());
+ boolean isOpening = isOpeningType(info.getType());
+ if (info.getRootLeash().isValid()) {
+ t.show(info.getRootLeash());
+ }
+ // changes should be ordered top-to-bottom in z
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ // Don't animate anything with an animating parent
+ if (change.getParent() != null) {
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
t.show(leash);
t.setMatrix(leash, 1, 0, 0, 1);
- if (isOpeningType(info.getType())) {
- t.setAlpha(leash, 0.f);
- startExampleAnimation(transitionToken, leash, true /* show */);
- } else {
- t.setAlpha(leash, 1.f);
- }
- } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
- if (!isOpeningType(info.getType())) {
- startExampleAnimation(transitionToken, leash, false /* show */);
- }
}
+ continue;
}
- t.apply();
- onFinish(transitionToken);
- });
+
+ t.reparent(leash, info.getRootLeash());
+ t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x,
+ change.getEndAbsBounds().top - info.getRootOffset().y);
+ // Put all the OPEN/SHOW on top
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ t.show(leash);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ if (isOpening) {
+ // put on top and fade in
+ t.setLayer(leash, info.getChanges().size() - i);
+ t.setAlpha(leash, 0.f);
+ startExampleAnimation(transitionToken, leash, true /* show */);
+ } else {
+ // put on bottom and leave it visible without fade
+ t.setLayer(leash, -i);
+ t.setAlpha(leash, 1.f);
+ }
+ } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+ if (isOpening) {
+ // put on bottom and leave visible without fade
+ t.setLayer(leash, -i);
+ } else {
+ // put on top and fade out
+ t.setLayer(leash, info.getChanges().size() - i);
+ startExampleAnimation(transitionToken, leash, false /* show */);
+ }
+ } else {
+ t.setLayer(leash, info.getChanges().size() - i);
+ }
+ }
+ t.apply();
+ onFinish(transitionToken);
}
- @MainThread
private void onFinish(IBinder transition) {
if (!mActiveTransitions.get(transition).isEmpty()) return;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -163,16 +198,32 @@ public class Transitions extends ITransitionPlayer.Stub {
mOrganizer.finishTransition(transition, null, null);
}
- @Override
- public void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+ private void requestStartTransition(int type, @NonNull IBinder transitionToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
type, transitionToken);
- mMainExecutor.execute(() -> {
- if (mActiveTransitions.containsKey(transitionToken)) {
- throw new RuntimeException("Transition already started " + transitionToken);
- }
- IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
- mActiveTransitions.put(transition, null);
- });
+
+ if (mActiveTransitions.containsKey(transitionToken)) {
+ throw new RuntimeException("Transition already started " + transitionToken);
+ }
+ IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
+ mActiveTransitions.put(transition, null);
+ }
+
+ @BinderThread
+ private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
+ @Override
+ public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
+ SurfaceControl.Transaction transaction) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ Transitions.this.onTransitionReady(iBinder, transitionInfo, transaction);
+ });
+ }
+
+ @Override
+ public void requestStartTransition(int i, IBinder iBinder) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ Transitions.this.requestStartTransition(i, iBinder);
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index acb9a5dae78c..834de3f15b1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -18,11 +18,11 @@ package com.android.wm.shell;
import static android.view.Display.DEFAULT_DISPLAY;
-import android.app.WindowConfiguration;
import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedStackListener;
/**
* The singleton wrapper to communicate between WindowManagerService and WMShell features
@@ -31,32 +31,30 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
public class WindowManagerShellWrapper {
private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
- public static final int WINDOWING_MODE_PINNED = WindowConfiguration.WINDOWING_MODE_PINNED;
-
/**
* Forwarder to which we can add multiple pinned stack listeners. Each listener will receive
* updates from the window manager service.
*/
- private PinnedStackListenerForwarder mPinnedStackListenerForwarder =
- new PinnedStackListenerForwarder();
+ private final PinnedStackListenerForwarder mPinnedStackListenerForwarder;
+
+ public WindowManagerShellWrapper(ShellExecutor shellMainExecutor) {
+ mPinnedStackListenerForwarder = new PinnedStackListenerForwarder(shellMainExecutor);
+ }
/**
* Adds a pinned stack listener, which will receive updates from the window manager service
* along with any other pinned stack listeners that were added via this method.
*/
- public void addPinnedStackListener(PinnedStackListenerForwarder.PinnedStackListener listener)
- throws
- RemoteException {
+ public void addPinnedStackListener(PinnedStackListener listener)
+ throws RemoteException {
mPinnedStackListenerForwarder.addListener(listener);
- WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
- DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
+ mPinnedStackListenerForwarder.register(DEFAULT_DISPLAY);
}
/**
* Removes a pinned stack listener.
*/
- public void removePinnedStackListener(
- PinnedStackListenerForwarder.PinnedStackListener listener) {
+ public void removePinnedStackListener(PinnedStackListener listener) {
mPinnedStackListenerForwarder.removeListener(listener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 357f777e1270..176c620fa119 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -23,6 +23,8 @@ import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import javax.inject.Inject;
+
/**
* Utility class to calculate general fling animation when the finger is released.
*/
@@ -368,6 +370,7 @@ public class FlingAnimationUtils {
float mX2;
float mY2;
+ @Inject
public Builder(DisplayMetrics displayMetrics) {
mDisplayMetrics = displayMetrics;
reset();
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 f199072f7bca..cfbf8452ddae 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
@@ -29,11 +29,13 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitLayout;
import java.io.PrintWriter;
@@ -42,7 +44,7 @@ import java.io.PrintWriter;
* {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
* Also includes all UI for managing the pair like the divider.
*/
-class AppPair implements ShellTaskOrganizer.TaskListener {
+class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChangeListener {
private static final String TAG = AppPair.class.getSimpleName();
private ActivityManager.RunningTaskInfo mRootTaskInfo;
@@ -55,7 +57,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
private final AppPairsController mController;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
- private AppPairLayout mAppPairLayout;
+ private SplitLayout mSplitLayout;
AppPair(AppPairsController controller) {
mController = controller;
@@ -92,11 +94,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
mTaskInfo1 = task1;
mTaskInfo2 = task2;
- mAppPairLayout = new AppPairLayout(
+ mSplitLayout = new SplitLayout(
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
- mDisplayController.getDisplay(mRootTaskInfo.displayId),
- mRootTaskInfo.configuration,
- mRootTaskLeash);
+ mRootTaskInfo.configuration, this, mRootTaskLeash);
final WindowContainerToken token1 = task1.token;
final WindowContainerToken token2 = task2.token;
@@ -107,8 +107,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
.reparent(token2, mRootTaskInfo.token, true /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
.setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
- .setBounds(token1, mAppPairLayout.getBounds1())
- .setBounds(token2, mAppPairLayout.getBounds2())
+ .setBounds(token1, mSplitLayout.getBounds1())
+ .setBounds(token2, mSplitLayout.getBounds2())
// Moving the root task to top after the child tasks were repareted , or the root
// task cannot be visible and focused.
.reorder(mRootTaskInfo.token, true);
@@ -117,6 +117,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
}
void unpair() {
+ unpair(null /* toTopToken */);
+ }
+
+ private void unpair(@Nullable WindowContainerToken toTopToken) {
final WindowContainerToken token1 = mTaskInfo1.token;
final WindowContainerToken token2 = mTaskInfo2.token;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -124,23 +128,16 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
// Reparent out of this container and reset windowing mode.
wct.setHidden(mRootTaskInfo.token, true)
.reorder(mRootTaskInfo.token, false)
- .reparent(token1, null, false /* onTop */)
- .reparent(token2, null, false /* onTop */)
+ .reparent(token1, null, token1 == toTopToken /* onTop */)
+ .reparent(token2, null, token2 == toTopToken /* onTop */)
.setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
.setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
mController.getTaskOrganizer().applyTransaction(wct);
mTaskInfo1 = null;
mTaskInfo2 = null;
- mAppPairLayout.release();
- mAppPairLayout = null;
- }
-
- void setVisible(boolean visible) {
- if (mAppPairLayout == null) {
- return;
- }
- mAppPairLayout.setDividerVisibility(visible);
+ mSplitLayout.release();
+ mSplitLayout = null;
}
@Override
@@ -160,37 +157,46 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
if (mTaskLeash1 == null || mTaskLeash2 == null) return;
- setVisible(true);
- final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
- final Rect dividerBounds = mAppPairLayout.getDividerBounds();
+ mSplitLayout.init();
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ final Rect dividerBounds = mSplitLayout.getDividerBounds();
// TODO: Is there more we need to do here?
- mSyncQueue.runInSync(t -> t
- .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
- mTaskInfo1.positionInParent.y)
- .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
- mTaskInfo2.positionInParent.y)
- .setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
- .show(mRootTaskLeash)
- .show(dividerLeash)
- .show(mTaskLeash1)
- .show(mTaskLeash2));
+ mSyncQueue.runInSync(t -> {
+ t.setLayer(dividerLeash, Integer.MAX_VALUE)
+ .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
+ mTaskInfo1.positionInParent.y)
+ .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
+ mTaskInfo2.positionInParent.y)
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .show(mRootTaskLeash)
+ .show(mTaskLeash1)
+ .show(mTaskLeash2);
+ });
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo.taskId == getRootTaskId()) {
+ if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+ mSyncQueue.runInSync(t -> {
+ if (taskInfo.isVisible) {
+ t.show(mRootTaskLeash);
+ } else {
+ t.hide(mRootTaskLeash);
+ }
+ });
+ }
mRootTaskInfo = taskInfo;
- if (mAppPairLayout != null
- && mAppPairLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- // Update bounds when there is root bounds or orientation changed.
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
+ // Update bounds when root bounds or its orientation changed.
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final SurfaceControl dividerLeash = mAppPairLayout.getDividerLeash();
- final Rect dividerBounds = mAppPairLayout.getDividerBounds();
- final Rect bounds1 = mAppPairLayout.getBounds1();
- final Rect bounds2 = mAppPairLayout.getBounds2();
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ final Rect dividerBounds = mSplitLayout.getDividerBounds();
+ final Rect bounds1 = mSplitLayout.getBounds1();
+ final Rect bounds2 = mSplitLayout.getBounds2();
wct.setBounds(mTaskInfo1.token, bounds1)
.setBounds(mTaskInfo2.token, bounds2);
@@ -198,7 +204,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
mSyncQueue.runInSync(t -> t
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top)
- .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top));
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ // Resets layer to divider bar to make sure it is always on top.
+ .setLayer(dividerLeash, Integer.MAX_VALUE));
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
@@ -240,4 +248,39 @@ class AppPair implements ShellTaskOrganizer.TaskListener {
public String toString() {
return TAG + "#" + getRootTaskId();
}
+
+ @Override
+ public void onSnappedToDismiss(boolean snappedToEnd) {
+ unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */);
+ }
+
+ @Override
+ public void onBoundsChanging(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect dividerBounds = layout.getDividerBounds();
+ final Rect bounds1 = layout.getBounds1();
+ final Rect bounds2 = layout.getBounds2();
+ mSyncQueue.runInSync(t -> t
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .setPosition(mTaskLeash1, bounds1.left, bounds1.top)
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ }
+
+ @Override
+ public void onBoundsChanged(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect dividerBounds = layout.getDividerBounds();
+ final Rect bounds1 = layout.getBounds1();
+ final Rect bounds2 = layout.getBounds2();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mTaskInfo1.token, bounds1)
+ .setBounds(mTaskInfo2.token, bounds2);
+ mController.getTaskOrganizer().applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
+ .setPosition(mTaskLeash1, bounds1.left, bounds1.top)
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java
deleted file mode 100644
index f8703f7ec0bc..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairLayout.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.apppairs;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Records and handles layout of a pair of apps.
- */
-// TODO(172704238): add tests
-final class AppPairLayout {
- private static final String DIVIDER_WINDOW_TITLE = "AppPairDivider";
- private final Context mContext;
- private final AppPairWindowManager mAppPairWindowManager;
- private final SurfaceControlViewHost mViewHost;
-
- private final int mDividerWindowWidth;
- private final int mDividerWindowInsets;
-
- private boolean mIsLandscape;
- private Rect mRootBounds;
- private DIVIDE_POLICY mDividePolicy;
-
- private DividerView mDividerView;
- private SurfaceControl mDividerLeash;
-
- AppPairLayout(
- Context context,
- Display display,
- Configuration configuration,
- SurfaceControl rootLeash) {
- mContext = context.createConfigurationContext(configuration);
- mIsLandscape = isLandscape(configuration);
- mRootBounds = configuration.windowConfiguration.getBounds();
- mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerWindowInsets = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
-
- mAppPairWindowManager = new AppPairWindowManager(configuration, rootLeash);
- mViewHost = new SurfaceControlViewHost(mContext, display, mAppPairWindowManager);
- mDividePolicy = DIVIDE_POLICY.MIDDLE;
- mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
- }
-
- boolean updateConfiguration(Configuration configuration) {
- mAppPairWindowManager.setConfiguration(configuration);
- final Rect rootBounds = configuration.windowConfiguration.getBounds();
- final boolean isLandscape = isLandscape(configuration);
- if (mIsLandscape == isLandscape && isIdenticalBounds(mRootBounds, rootBounds)) {
- return false;
- }
-
- mIsLandscape = isLandscape;
- mRootBounds = rootBounds;
- mDividePolicy.update(mIsLandscape, mRootBounds, mDividerWindowWidth, mDividerWindowInsets);
- mViewHost.relayout(
- mDividePolicy.mDividerBounds.width(),
- mDividePolicy.mDividerBounds.height());
- // TODO(172704238): handle divider bar rotation.
- return true;
- }
-
- Rect getBounds1() {
- return mDividePolicy.mBounds1;
- }
-
- Rect getBounds2() {
- return mDividePolicy.mBounds2;
- }
-
- Rect getDividerBounds() {
- return mDividePolicy.mDividerBounds;
- }
-
- SurfaceControl getDividerLeash() {
- return mDividerLeash;
- }
-
- void release() {
- if (mViewHost == null) return;
- mViewHost.release();
- }
-
- void setDividerVisibility(boolean visible) {
- if (mDividerView == null) {
- initDivider();
- }
- if (visible) {
- mDividerView.show();
- } else {
- mDividerView.hide();
- }
- }
-
- private void initDivider() {
- final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
- .inflate(R.layout.split_divider, null);
-
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- mDividePolicy.mDividerBounds.width(),
- mDividePolicy.mDividerBounds.height(),
- TYPE_DOCK_DIVIDER,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.setTitle(DIVIDER_WINDOW_TITLE);
- lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
-
- mViewHost.setView(dividerView, lp);
- mDividerView = dividerView;
- mDividerLeash = mAppPairWindowManager.getSurfaceControl(mViewHost.getWindowToken());
- }
-
- private static boolean isLandscape(Configuration configuration) {
- return configuration.orientation == ORIENTATION_LANDSCAPE;
- }
-
- private static boolean isIdenticalBounds(Rect bounds1, Rect bounds2) {
- return bounds1.left == bounds2.left && bounds1.top == bounds2.top
- && bounds1.right == bounds2.right && bounds1.bottom == bounds2.bottom;
- }
-
- /**
- * Indicates the policy of placing divider bar and corresponding split-screens.
- */
- // TODO(172704238): add more divide policy and provide snap to resize feature for divider bar.
- enum DIVIDE_POLICY {
- MIDDLE;
-
- void update(boolean isLandscape, Rect rootBounds, int dividerWindowWidth,
- int dividerWindowInsets) {
- final int dividerOffset = dividerWindowWidth / 2;
- final int boundsOffset = dividerOffset - dividerWindowInsets;
-
- mDividerBounds = new Rect(rootBounds);
- mBounds1 = new Rect(rootBounds);
- mBounds2 = new Rect(rootBounds);
-
- switch (this) {
- case MIDDLE:
- default:
- if (isLandscape) {
- mDividerBounds.left = rootBounds.width() / 2 - dividerOffset;
- mDividerBounds.right = rootBounds.width() / 2 + dividerOffset;
- mBounds1.left = rootBounds.width() / 2 + boundsOffset;
- mBounds2.right = rootBounds.width() / 2 - boundsOffset;
- } else {
- mDividerBounds.top = rootBounds.height() / 2 - dividerOffset;
- mDividerBounds.bottom = rootBounds.height() / 2 + dividerOffset;
- mBounds1.bottom = rootBounds.height() / 2 - boundsOffset;
- mBounds2.top = rootBounds.height() / 2 + boundsOffset;
- }
- }
- }
-
- Rect mDividerBounds;
- Rect mBounds1;
- Rect mBounds2;
- }
-
- /**
- * WindowManger for app pair. Holds view hierarchy for the root task.
- */
- private static final class AppPairWindowManager extends WindowlessWindowManager {
- AppPairWindowManager(Configuration config, SurfaceControl rootSurface) {
- super(config, rootSurface, null /* hostInputToken */);
- }
-
- @Override
- public void setTouchRegion(IBinder window, Region region) {
- super.setTouchRegion(window, region);
- }
-
- @Override
- public SurfaceControl getSurfaceControl(IWindow window) {
- return super.getSurfaceControl(window);
- }
-
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index af06764145e3..f5aa852c87ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -20,11 +20,14 @@ import android.app.ActivityManager;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
/**
* Interface to engage app pairs feature.
*/
+@ExternalThread
public interface AppPairs {
/** Pairs indicated tasks. */
boolean pair(int task1, int task2);
@@ -36,6 +39,4 @@ public interface AppPairs {
void dump(@NonNull PrintWriter pw, String prefix);
/** Called when the shell organizer has been registered. */
void onOrganizerRegistered();
- /** Called when the visibility of the keyguard changes. */
- void onKeyguardVisibilityChanged(boolean showing);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index 925a4f36d5e6..f2f09820639a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.apppairs;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager;
@@ -30,15 +28,13 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerCallback;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import java.io.PrintWriter;
/**
* Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
*/
-public class AppPairsController implements AppPairs, TaskStackListenerCallback {
+public class AppPairsController implements AppPairs {
private static final String TAG = AppPairsController.class.getSimpleName();
private final ShellTaskOrganizer mTaskOrganizer;
@@ -48,14 +44,12 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
// Active app-pairs mapped by root task id key.
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
private final DisplayController mDisplayController;
- private int mForegroundTaskId = INVALID_TASK_ID;
public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, TaskStackListenerImpl taskStackListener) {
+ DisplayController displayController) {
mTaskOrganizer = organizer;
mSyncQueue = syncQueue;
mDisplayController = displayController;
- taskStackListener.addListener(this);
}
@Override
@@ -71,27 +65,6 @@ public class AppPairsController implements AppPairs, TaskStackListenerCallback {
}
@Override
- public void onTaskMovedToFront(int taskId) {
- mForegroundTaskId = INVALID_TASK_ID;
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- final AppPair candidate = mActiveAppPairs.valueAt(i);
- final boolean containForegroundTask = candidate.contains(taskId);
- candidate.setVisible(containForegroundTask);
- if (containForegroundTask) {
- mForegroundTaskId = candidate.getRootTaskId();
- }
- }
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (mForegroundTaskId == INVALID_TASK_ID) {
- return;
- }
- mActiveAppPairs.get(mForegroundTaskId).setVisible(!showing);
- }
-
- @Override
public boolean pair(int taskId1, int taskId2) {
final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java
deleted file mode 100644
index 41b5e47e7fa9..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/DividerView.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.apppairs;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Stack divider for app pair.
- */
-public class DividerView extends FrameLayout {
- public DividerView(@NonNull Context context) {
- super(context);
- }
-
- public DividerView(@NonNull Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- void show() {
- post(() -> setVisibility(View.VISIBLE));
- }
-
- void hide() {
- post(() -> setVisibility(View.INVISIBLE));
- }
-}
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 aa7355b61eda..40b41e11c8aa 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
@@ -1213,7 +1213,7 @@ public class BubbleController implements Bubbles {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (mStackView != null) {
- mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
+ mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 92d15c5feaca..fa5ac449cd54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -30,6 +30,8 @@ import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -40,6 +42,7 @@ import java.util.function.IntConsumer;
/**
* Interface to engage bubbles feature.
*/
+@ExternalThread
public interface Bubbles {
@Retention(SOURCE)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
deleted file mode 100644
index 96b9f86673fc..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.common;
-
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-
-import android.annotation.NonNull;
-import android.os.HandlerThread;
-import android.util.Singleton;
-
-/**
- * A singleton thread for Shell to run animations on.
- */
-public class AnimationThread extends HandlerThread {
- private ShellExecutor mExecutor;
-
- private AnimationThread() {
- super("wmshell.anim", THREAD_PRIORITY_DISPLAY);
- }
-
- /** Get the singleton instance of this thread */
- public static AnimationThread instance() {
- return sAnimationThreadSingleton.get();
- }
-
- /**
- * @return a shared {@link ShellExecutor} associated with this thread
- * @hide
- */
- @NonNull
- public ShellExecutor getExecutor() {
- if (mExecutor == null) {
- mExecutor = new HandlerExecutor(getThreadHandler());
- }
- return mExecutor;
- }
-
- private static final Singleton<AnimationThread> sAnimationThreadSingleton =
- new Singleton<AnimationThread>() {
- @Override
- protected AnimationThread create() {
- final AnimationThread animThread = new AnimationThread();
- animThread.start();
- return animThread;
- }
- };
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 3263f79888d6..cb4584c41184 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -23,6 +23,10 @@ import android.view.IDisplayWindowRotationController;
import android.view.IWindowManager;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
import java.util.ArrayList;
/**
@@ -35,39 +39,18 @@ public class DisplayChangeController {
private final Handler mHandler;
private final IWindowManager mWmService;
+ private final IDisplayWindowRotationController mControllerImpl;
private final ArrayList<OnDisplayChangingListener> mRotationListener =
new ArrayList<>();
private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>();
- private final IDisplayWindowRotationController mDisplayRotationController =
- new IDisplayWindowRotationController.Stub() {
- @Override
- public void onRotateDisplay(int displayId, final int fromRotation,
- final int toRotation, IDisplayWindowRotationCallback callback) {
- mHandler.post(() -> {
- WindowContainerTransaction t = new WindowContainerTransaction();
- synchronized (mRotationListener) {
- mTmpListeners.clear();
- // Make a local copy in case the handlers add/remove themselves.
- mTmpListeners.addAll(mRotationListener);
- }
- for (OnDisplayChangingListener c : mTmpListeners) {
- c.onRotateDisplay(displayId, fromRotation, toRotation, t);
- }
- try {
- callback.continueRotateDisplay(toRotation, t);
- } catch (RemoteException e) {
- }
- });
- }
- };
-
public DisplayChangeController(Handler mainHandler, IWindowManager wmService) {
mHandler = mainHandler;
mWmService = wmService;
+ mControllerImpl = new DisplayWindowRotationControllerImpl();
try {
- mWmService.setDisplayWindowRotationController(mDisplayRotationController);
+ mWmService.setDisplayWindowRotationController(mControllerImpl);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register rotation controller");
}
@@ -91,10 +74,41 @@ public class DisplayChangeController {
}
}
+ private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
+ IDisplayWindowRotationCallback callback) {
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ synchronized (mRotationListener) {
+ mTmpListeners.clear();
+ // Make a local copy in case the handlers add/remove themselves.
+ mTmpListeners.addAll(mRotationListener);
+ }
+ for (OnDisplayChangingListener c : mTmpListeners) {
+ c.onRotateDisplay(displayId, fromRotation, toRotation, t);
+ }
+ try {
+ callback.continueRotateDisplay(toRotation, t);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @BinderThread
+ private class DisplayWindowRotationControllerImpl
+ extends IDisplayWindowRotationController.Stub {
+ @Override
+ public void onRotateDisplay(int displayId, final int fromRotation,
+ final int toRotation, IDisplayWindowRotationCallback callback) {
+ mHandler.post(() -> {
+ DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
+ callback);
+ });
+ }
+ }
+
/**
* Give a listener a chance to queue up configuration changes to execute as part of a
* display rotation. The contents of {@link #onRotateDisplay} must run synchronously.
*/
+ @ShellMainThread
public interface OnDisplayChangingListener {
/**
* Called before the display is rotated. Contents of this method must run synchronously.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 418973204add..a413c052cb6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -28,7 +28,10 @@ import android.view.Display;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
+import androidx.annotation.BinderThread;
+
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.ArrayList;
@@ -45,6 +48,7 @@ public class DisplayController {
private final Context mContext;
private final IWindowManager mWmService;
private final DisplayChangeController mChangeController;
+ private final IDisplayWindowListener mDisplayContainerListener;
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
@@ -57,119 +61,13 @@ public class DisplayController {
return displayManager.getDisplay(displayId);
}
- private final IDisplayWindowListener mDisplayContainerListener =
- new IDisplayWindowListener.Stub() {
- @Override
- public void onDisplayAdded(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) != null) {
- return;
- }
- Display display = getDisplay(displayId);
- if (display == null) {
- // It's likely that the display is private to some app and thus not
- // accessible by system-ui.
- return;
- }
- DisplayRecord record = new DisplayRecord();
- record.mDisplayId = displayId;
- record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
- : mContext.createDisplayContext(display);
- record.mDisplayLayout = new DisplayLayout(record.mContext, display);
- mDisplays.put(displayId, record);
- for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
- }
- }
- });
- }
-
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- DisplayRecord dr = mDisplays.get(displayId);
- if (dr == null) {
- Slog.w(TAG, "Skipping Display Configuration change on non-added"
- + " display.");
- return;
- }
- Display display = getDisplay(displayId);
- if (display == null) {
- Slog.w(TAG, "Skipping Display Configuration change on invalid"
- + " display. It may have been removed.");
- return;
- }
- Context perDisplayContext = mContext;
- if (displayId != Display.DEFAULT_DISPLAY) {
- perDisplayContext = mContext.createDisplayContext(display);
- }
- dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
- dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
- for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
- mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
- displayId, newConfig);
- }
- }
- });
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null) {
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
- }
- mDisplays.remove(displayId);
- }
- });
- }
-
- @Override
- public void onFixedRotationStarted(int displayId, int newRotation) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
- Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
- + " display, displayId=" + displayId);
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onFixedRotationStarted(
- displayId, newRotation);
- }
- }
- });
- }
-
- @Override
- public void onFixedRotationFinished(int displayId) {
- mHandler.post(() -> {
- synchronized (mDisplays) {
- if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
- Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
- + " display, displayId=" + displayId);
- return;
- }
- for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
- }
- }
- });
- }
- };
-
public DisplayController(Context context, Handler handler,
IWindowManager wmService) {
mHandler = handler;
mContext = context;
mWmService = wmService;
mChangeController = new DisplayChangeController(mHandler, mWmService);
+ mDisplayContainerListener = new DisplayWindowListenerImpl();
try {
mWmService.registerDisplayWindowListener(mDisplayContainerListener);
} catch (RemoteException e) {
@@ -232,18 +130,146 @@ public class DisplayController {
mChangeController.removeRotationListener(controller);
}
+ private void onDisplayAdded(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) != null) {
+ return;
+ }
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ // It's likely that the display is private to some app and thus not
+ // accessible by system-ui.
+ return;
+ }
+ DisplayRecord record = new DisplayRecord();
+ record.mDisplayId = displayId;
+ record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+ : mContext.createDisplayContext(display);
+ record.mDisplayLayout = new DisplayLayout(record.mContext, display);
+ mDisplays.put(displayId, record);
+ for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+ mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
+ }
+ }
+ }
+
+ private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ synchronized (mDisplays) {
+ DisplayRecord dr = mDisplays.get(displayId);
+ if (dr == null) {
+ Slog.w(TAG, "Skipping Display Configuration change on non-added"
+ + " display.");
+ return;
+ }
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ Slog.w(TAG, "Skipping Display Configuration change on invalid"
+ + " display. It may have been removed.");
+ return;
+ }
+ Context perDisplayContext = mContext;
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ perDisplayContext = mContext.createDisplayContext(display);
+ }
+ dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+ dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
+ for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+ mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
+ displayId, newConfig);
+ }
+ }
+ }
+
+ private void onDisplayRemoved(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null) {
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
+ }
+ mDisplays.remove(displayId);
+ }
+ }
+
+ private void onFixedRotationStarted(int displayId, int newRotation) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationStarted(
+ displayId, newRotation);
+ }
+ }
+ }
+
+ private void onFixedRotationFinished(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
+ }
+ }
+ }
+
private static class DisplayRecord {
int mDisplayId;
Context mContext;
DisplayLayout mDisplayLayout;
}
+ @BinderThread
+ private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayAdded(displayId);
+ });
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig);
+ });
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onDisplayRemoved(displayId);
+ });
+ }
+
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) {
+ mHandler.post(() -> {
+ DisplayController.this.onFixedRotationStarted(displayId, newRotation);
+ });
+ }
+
+ @Override
+ public void onFixedRotationFinished(int displayId) {
+ mHandler.post(() -> {
+ DisplayController.this.onFixedRotationFinished(displayId);
+ });
+ }
+ }
+
/**
* Gets notified when a display is added/removed to the WM hierarchy and when a display's
* window-configuration changes.
*
* @see IDisplayWindowListener
*/
+ @ShellMainThread
public interface OnDisplaysChangedListener {
/**
* Called when a display has been added to the WM hierarchy.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index ea18a19c2ee5..3fbd7ed0ec5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -24,7 +24,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
@@ -40,6 +39,8 @@ import android.view.WindowInsets;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import androidx.annotation.BinderThread;
+
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
@@ -197,6 +198,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mRotation = initialRotation;
}
+ @BinderThread
@Override
public void insetsChanged(InsetsState insetsState) {
mExecutor.execute(() -> {
@@ -204,6 +206,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return;
}
+ mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+
final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
final Rect newFrame = newSource.getFrame();
final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
@@ -216,6 +220,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
});
}
+ @BinderThread
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
@@ -266,6 +271,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
+ @BinderThread
@Override
public void showInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
@@ -275,6 +281,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
}
+ @BinderThread
@Override
public void hideInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
@@ -284,6 +291,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
}
+ @BinderThread
@Override
public void topFocusedWindowChanged(String packageName) {
// no-op
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 cd75840b8c71..fa0a75c2d364 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
@@ -28,6 +28,17 @@ public class HandlerExecutor implements ShellExecutor {
}
@Override
+ public void execute(@NonNull Runnable command) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ command.run();
+ return;
+ }
+ if (!mHandler.post(command)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+
+ @Override
public void executeDelayed(@NonNull Runnable r, long delayMillis) {
if (!mHandler.postDelayed(r, delayMillis)) {
throw new RuntimeException(mHandler + " is probably exiting");
@@ -38,11 +49,4 @@ public class HandlerExecutor implements ShellExecutor {
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
-
- @Override
- public void execute(@NonNull Runnable command) {
- if (!mHandler.post(command)) {
- throw new RuntimeException(mHandler + " is probably exiting");
- }
- }
}
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 aafe2407a1ea..22b831b7565e 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,13 +16,40 @@
package com.android.wm.shell.common;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* Super basic Executor interface that adds support for delayed execution and removing callbacks.
* Intended to wrap Handler while better-supporting testing.
*/
public interface ShellExecutor extends Executor {
+
+ /**
+ * Executes the given runnable. If the caller is running on the same looper as this executor,
+ * the runnable must be executed immediately.
+ */
+ @Override
+ void execute(Runnable runnable);
+
+ /**
+ * Executes the given runnable in a blocking call. If the caller is running on the same looper
+ * as this executor, the runnable must be executed immediately.
+ *
+ * @throws InterruptedException if runnable does not return in the time specified by
+ * {@param waitTimeout}
+ */
+ default void executeBlocking(Runnable runnable, int waitTimeout, TimeUnit waitTimeUnit)
+ throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ execute(() -> {
+ runnable.run();
+ latch.countDown();
+ });
+ latch.await(waitTimeout, waitTimeUnit);
+ }
+
/**
* See {@link android.os.Handler#postDelayed(Runnable, long)}.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 9cb125087cd9..7321dc88770d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -24,6 +24,10 @@ import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
import java.util.ArrayList;
/**
@@ -151,6 +155,7 @@ public final class SyncTransactionQueue {
mHandler.postDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
+ @BinderThread
@Override
public void onTransactionReady(int id,
@NonNull SurfaceControl.Transaction t) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 0f6dd93f9c16..5e077188c415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -19,12 +19,10 @@ package com.android.wm.shell.common;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ITaskStackListener;
-import android.app.TaskInfo;
import android.content.ComponentName;
import android.os.IBinder;
import androidx.annotation.BinderThread;
-import androidx.annotation.MainThread;
/**
* An interface to track task stack changes. Classes should implement this instead of
@@ -32,85 +30,61 @@ import androidx.annotation.MainThread;
*/
public interface TaskStackListenerCallback {
- @MainThread
default void onRecentTaskListUpdated() { }
- @MainThread
default void onRecentTaskListFrozenChanged(boolean frozen) { }
@BinderThread
default void onTaskStackChangedBackground() { }
- @MainThread
default void onTaskStackChanged() { }
- @MainThread
default void onTaskProfileLocked(int taskId, int userId) { }
- @MainThread
default void onTaskDisplayChanged(int taskId, int newDisplayId) { }
- @MainThread
default void onTaskCreated(int taskId, ComponentName componentName) { }
- @MainThread
default void onTaskRemoved(int taskId) { }
- @MainThread
default void onTaskMovedToFront(int taskId) { }
- @MainThread
default void onTaskMovedToFront(RunningTaskInfo taskInfo) {
onTaskMovedToFront(taskInfo.taskId);
}
- @MainThread
default void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { }
- @MainThread
default void onTaskSnapshotChanged(int taskId, ActivityManager.TaskSnapshot snapshot) { }
- @MainThread
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { }
- @MainThread
default void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) { }
- @MainThread
default void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
- @MainThread
default void onActivityUnpinned() { }
- @MainThread
default void onActivityForcedResizable(String packageName, int taskId, int reason) { }
- @MainThread
default void onActivityDismissingDockedStack() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayFailed() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo) {
onActivityLaunchOnSecondaryDisplayFailed();
}
- @MainThread
default void onActivityLaunchOnSecondaryDisplayRerouted() { }
- @MainThread
default void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo) {
onActivityLaunchOnSecondaryDisplayRerouted();
}
- @MainThread
default void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
- @MainThread
default void onActivityRotation(int displayId) { }
- @MainThread
default void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
new file mode 100644
index 000000000000..4009ad21b9b8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
@@ -0,0 +1,18 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
+ * instead of the app vsync.
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ChoreographerSfVsync {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
new file mode 100644
index 000000000000..7560f71d1f98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or class that is called from an external thread to the Shell threads. */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExternalThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
new file mode 100644
index 000000000000..0479f8780c79
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellAnimationThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
new file mode 100644
index 000000000000..423f4ce3bfd4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
@@ -0,0 +1,15 @@
+package com.android.wm.shell.common.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellMainThread {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
new file mode 100644
index 000000000000..50d9fe8629ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -0,0 +1,171 @@
+/*
+ * 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.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceControlViewHost;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+
+/**
+ * Stack divider for app pair.
+ */
+// TODO(b/172704238): add handle view to indicate touching status.
+public class DividerView extends FrameLayout implements View.OnTouchListener {
+ private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+ private SplitLayout mSplitLayout;
+ private SurfaceControlViewHost mViewHost;
+ private DragListener mDragListener;
+
+ private VelocityTracker mVelocityTracker;
+ private boolean mMoving;
+ private int mStartPos;
+
+ public DividerView(@NonNull Context context) {
+ super(context);
+ }
+
+ public DividerView(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public DividerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /** Sets up essential dependencies of the divider bar. */
+ public void setup(
+ SplitLayout layout,
+ SurfaceControlViewHost viewHost,
+ @Nullable DragListener dragListener) {
+ mSplitLayout = layout;
+ mViewHost = viewHost;
+ mDragListener = dragListener;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setOnTouchListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mSplitLayout == null) {
+ return false;
+ }
+
+ final int action = event.getAction() & MotionEvent.ACTION_MASK;
+ final boolean isLandscape = isLandscape();
+ // Using raw xy to prevent lost track of motion events while moving divider bar.
+ final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+ setSlippery(false);
+ mStartPos = touchPos;
+ mMoving = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mVelocityTracker.addMovement(event);
+ if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) {
+ mStartPos = touchPos;
+ mMoving = true;
+ if (mDragListener != null) {
+ mDragListener.onDragStart();
+ }
+ }
+ if (mMoving) {
+ final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ mSplitLayout.updateDividePosition(position);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(1000 /* units */);
+ final float velocity = isLandscape
+ ? mVelocityTracker.getXVelocity()
+ : mVelocityTracker.getYVelocity();
+ setSlippery(true);
+ mMoving = false;
+ if (mDragListener != null) {
+ mDragListener.onDragEnd();
+ }
+
+ final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ final DividerSnapAlgorithm.SnapTarget snapTarget =
+ mSplitLayout.findSnapTarget(position, velocity);
+ mSplitLayout.setSnapTarget(snapTarget);
+ break;
+ }
+ return true;
+ }
+
+ private void setSlippery(boolean slippery) {
+ if (mViewHost == null) {
+ return;
+ }
+
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+ final boolean isSlippery = (lp.flags & FLAG_SLIPPERY) != 0;
+ if (isSlippery == slippery) {
+ return;
+ }
+
+ if (slippery) {
+ lp.flags |= FLAG_SLIPPERY;
+ } else {
+ lp.flags &= ~FLAG_SLIPPERY;
+ }
+ mViewHost.relayout(lp);
+ }
+
+ private boolean isLandscape() {
+ return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ /** Monitors dragging action of the divider bar. */
+ // TODO(b/172704238): add listeners to deal with resizing state of the app windows.
+ public interface DragListener {
+ /** Called when start dragging. */
+ void onDragStart();
+ /** Called when stop dragging. */
+ void onDragEnd();
+ }
+}
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
new file mode 100644
index 000000000000..e11037f55cfa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -0,0 +1,205 @@
+/*
+ * 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.common.split;
+
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+
+/**
+ * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
+ * divide position changes.
+ */
+public class SplitLayout {
+ private final int mDividerWindowWidth;
+ private final int mDividerInsets;
+ private final int mDividerSize;
+
+ private final Rect mRootBounds = new Rect();
+ private final Rect mDividerBounds = new Rect();
+ private final Rect mBounds1 = new Rect();
+ private final Rect mBounds2 = new Rect();
+ private final LayoutChangeListener mLayoutChangeListener;
+ private final SplitWindowManager mSplitWindowManager;
+
+ private Context mContext;
+ private DividerSnapAlgorithm mDividerSnapAlgorithm;
+ private int mDividePosition;
+
+ public SplitLayout(Context context, Configuration configuration,
+ LayoutChangeListener layoutChangeListener, SurfaceControl rootLeash) {
+ mContext = context.createConfigurationContext(configuration);
+ mLayoutChangeListener = layoutChangeListener;
+ mSplitWindowManager = new SplitWindowManager(mContext, configuration, rootLeash);
+
+ mDividerWindowWidth = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ mDividerInsets = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ mDividerSize = mDividerWindowWidth - mDividerInsets * 2;
+
+ mRootBounds.set(configuration.windowConfiguration.getBounds());
+ mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds);
+ mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividePosition);
+ }
+
+ /** Gets bounds of the primary split. */
+ public Rect getBounds1() {
+ return mBounds1;
+ }
+
+ /** Gets bounds of the secondary split. */
+ public Rect getBounds2() {
+ return mBounds2;
+ }
+
+ /** Gets bounds of divider window. */
+ public Rect getDividerBounds() {
+ return mDividerBounds;
+ }
+
+ /** Returns leash of the current divider bar. */
+ @Nullable
+ public SurfaceControl getDividerLeash() {
+ return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
+ }
+
+ int getDividePosition() {
+ return mDividePosition;
+ }
+
+ /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
+ public boolean updateConfiguration(Configuration configuration) {
+ final Rect rootBounds = configuration.windowConfiguration.getBounds();
+ if (mRootBounds.equals(rootBounds)) {
+ return false;
+ }
+
+ mContext = mContext.createConfigurationContext(configuration);
+ mSplitWindowManager.setConfiguration(configuration);
+ mRootBounds.set(rootBounds);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds);
+ mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividePosition);
+ release();
+ init();
+ return true;
+ }
+
+ /** Updates recording bounds of divider window and both of the splits. */
+ private void updateBounds(int position) {
+ mDividerBounds.set(mRootBounds);
+ mBounds1.set(mRootBounds);
+ mBounds2.set(mRootBounds);
+ if (isLandscape(mRootBounds)) {
+ mDividerBounds.left = position - mDividerInsets;
+ mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
+ mBounds1.right = mBounds1.left + position;
+ mBounds2.left = mBounds1.right + mDividerSize;
+ } else {
+ mDividerBounds.top = position - mDividerInsets;
+ mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth;
+ mBounds1.bottom = mBounds1.top + position;
+ mBounds2.top = mBounds1.bottom + mDividerSize;
+ }
+ }
+
+ /** Inflates {@link DividerView} on the root surface. */
+ public void init() {
+ mSplitWindowManager.init(this);
+ }
+
+ /** Releases the surface holding the current {@link DividerView}. */
+ public void release() {
+ mSplitWindowManager.release();
+ }
+
+ /**
+ * Updates bounds with the passing position. Usually used to update recording bounds while
+ * performing animation or dragging divider bar to resize the splits.
+ */
+ public void updateDividePosition(int position) {
+ updateBounds(position);
+ mLayoutChangeListener.onBoundsChanging(this);
+ }
+
+ /**
+ * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
+ * target indicates dismissing split.
+ */
+ public void setSnapTarget(DividerSnapAlgorithm.SnapTarget snapTarget) {
+ switch(snapTarget.flag) {
+ case FLAG_DISMISS_START:
+ mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */);
+ break;
+ case FLAG_DISMISS_END:
+ mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */);
+ break;
+ default:
+ mDividePosition = snapTarget.position;
+ updateBounds(mDividePosition);
+ mLayoutChangeListener.onBoundsChanged(this);
+ break;
+ }
+ }
+
+ /**
+ * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
+ */
+ public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) {
+ return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity);
+ }
+
+ private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) {
+ final boolean isLandscape = isLandscape(rootBounds);
+ return new DividerSnapAlgorithm(
+ resources,
+ rootBounds.width(),
+ rootBounds.height(),
+ mDividerSize,
+ !isLandscape,
+ new Rect() /* insets */,
+ isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+ }
+
+ private static boolean isLandscape(Rect bounds) {
+ return bounds.width() > bounds.height();
+ }
+
+ /** Listens layout change event. */
+ public interface LayoutChangeListener {
+ /** Calls when dismissing split. */
+ void onSnappedToDismiss(boolean snappedToEnd);
+ /** Calls when the bounds is changing due to animation or dragging divider bar. */
+ void onBoundsChanging(SplitLayout layout);
+ /** Calls when the target bounds changed. */
+ void onBoundsChanged(SplitLayout layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
new file mode 100644
index 000000000000..e4121986bcb7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -0,0 +1,116 @@
+/*
+ * 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.common.split;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split.
+ */
+public final class SplitWindowManager extends WindowlessWindowManager {
+ private static final String DIVIDER_WINDOW_TITLE = "SplitDivider";
+
+ private Context mContext;
+ private SurfaceControlViewHost mViewHost;
+
+ public SplitWindowManager(Context context, Configuration config, SurfaceControl rootSurface) {
+ super(config, rootSurface, null /* hostInputToken */);
+ mContext = context.createConfigurationContext(config);
+ }
+
+ @Override
+ public void setTouchRegion(IBinder window, Region region) {
+ super.setTouchRegion(window, region);
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl(IWindow window) {
+ return super.getSurfaceControl(window);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ mContext = mContext.createConfigurationContext(configuration);
+ }
+
+ /** Inflates {@link DividerView} on to the root surface. */
+ void init(SplitLayout splitLayout) {
+ if (mViewHost == null) {
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+ }
+
+ final Rect dividerBounds = splitLayout.getDividerBounds();
+ final DividerView dividerView = (DividerView) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_divider, null /* root */);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
+ | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.setTitle(DIVIDER_WINDOW_TITLE);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ mViewHost.setView(dividerView, lp);
+ dividerView.setup(splitLayout, mViewHost, null /* dragListener */);
+ }
+
+ /**
+ * Releases the surface control of the current {@link DividerView} and tear down the view
+ * hierarchy.
+ */
+ void release() {
+ if (mViewHost == null) return;
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ /**
+ * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
+ * feasible.
+ */
+ @Nullable
+ SurfaceControl getSurfaceControl() {
+ return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken());
+ }
+}
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 625c0a7b5d19..a89c8bb29c1b 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
@@ -54,7 +54,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreen;
-import java.util.Objects;
import java.util.Optional;
/**
@@ -108,16 +107,22 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
DragLayout dragLayout = new DragLayout(context, mSplitScreen);
rootView.addView(dragLayout,
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- wm.addView(rootView, layoutParams);
-
- mDisplayDropTargets.put(displayId,
- new PerDisplay(displayId, context, wm, rootView, dragLayout));
+ try {
+ wm.addView(rootView, layoutParams);
+ mDisplayDropTargets.put(displayId,
+ new PerDisplay(displayId, context, wm, rootView, dragLayout));
+ } catch (WindowManager.InvalidDisplayException e) {
+ Slog.w(TAG, "Unable to add view for display id: " + displayId);
+ }
}
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display changed: %d", displayId);
final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ if (pd == null) {
+ return;
+ }
pd.rootView.requestApplyInsets();
}
@@ -125,6 +130,9 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
public void onDisplayRemoved(int displayId) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display removed: %d", displayId);
final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ if (pd == null) {
+ return;
+ }
pd.wm.removeViewImmediate(pd.rootView);
mDisplayDropTargets.remove(displayId);
}
@@ -139,6 +147,10 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
final PerDisplay pd = mDisplayDropTargets.get(displayId);
final ClipDescription description = event.getClipDescription();
+ if (pd == null) {
+ return false;
+ }
+
if (event.getAction() == ACTION_DRAG_STARTED) {
final boolean hasValidClipData = event.getClipData().getItemCount() > 0
&& (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
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 8a547b4477fd..7b3b5dbfa51c 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
@@ -18,12 +18,10 @@ package com.android.wm.shell.draganddrop;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
@@ -78,7 +76,7 @@ public class DragAndDropPolicy {
private static final String TAG = DragAndDropPolicy.class.getSimpleName();
private final Context mContext;
- private final IActivityTaskManager mIActivityTaskManager;
+ private final ActivityTaskManager mActivityTaskManager;
private final Starter mStarter;
private final SplitScreen mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
@@ -86,15 +84,15 @@ public class DragAndDropPolicy {
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
- this(context, ActivityTaskManager.getService(), splitScreen,
+ this(context, ActivityTaskManager.getInstance(), splitScreen,
new DefaultStarter(context, splitScreen));
}
@VisibleForTesting
- DragAndDropPolicy(Context context, IActivityTaskManager activityTaskManager,
+ DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
SplitScreen splitScreen, Starter starter) {
mContext = context;
- mIActivityTaskManager = activityTaskManager;
+ mActivityTaskManager = activityTaskManager;
mSplitScreen = splitScreen;
mStarter = starter;
}
@@ -103,7 +101,7 @@ public class DragAndDropPolicy {
* Starts a new drag session with the given initial drag data.
*/
void start(DisplayLayout displayLayout, ClipData data) {
- mSession = new DragSession(mContext, mIActivityTaskManager, displayLayout, data);
+ mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
}
@@ -271,7 +269,7 @@ public class DragAndDropPolicy {
*/
private static class DragSession {
private final Context mContext;
- private final IActivityTaskManager mIActivityTaskManager;
+ private final ActivityTaskManager mActivityTaskManager;
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
@@ -285,10 +283,10 @@ public class DragAndDropPolicy {
boolean dragItemSupportsSplitscreen;
boolean isPhone;
- DragSession(Context context, IActivityTaskManager activityTaskManager,
+ DragSession(Context context, ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
mContext = context;
- mIActivityTaskManager = activityTaskManager;
+ mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
displayLayout = dispLayout;
}
@@ -298,19 +296,14 @@ public class DragAndDropPolicy {
*/
void update() {
- try {
- List<ActivityManager.RunningTaskInfo> tasks =
- mIActivityTaskManager.getFilteredTasks(1,
- false /* filterOnlyVisibleRecents */);
- if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
- runningTaskId = task.taskId;
- runningTaskIsResizeable = task.isResizeable;
- }
- } catch (RemoteException e) {
- // Fall through
+ List<ActivityManager.RunningTaskInfo> tasks =
+ mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ if (!tasks.isEmpty()) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(0);
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ runningTaskId = task.taskId;
+ runningTaskIsResizeable = task.isResizeable;
}
final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 38e0519b7a90..3a2f0da6bf03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -20,11 +20,14 @@ import android.content.res.Configuration;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
/**
* Interface to engage hide display cutout feature.
*/
+@ExternalThread
public interface HideDisplayCutout {
/**
* Notifies {@link Configuration} changed.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 090d2270817b..4e62ea6e7233 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -272,8 +272,9 @@ class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
@VisibleForTesting
void applyBoundsAndOffsets(WindowContainerToken token, SurfaceControl leash,
WindowContainerTransaction wct, SurfaceControl.Transaction t) {
- wct.setBounds(token, mCurrentDisplayBounds.isEmpty() ? null : mCurrentDisplayBounds);
+ wct.setBounds(token, mCurrentDisplayBounds);
t.setPosition(leash, mOffsetX, mOffsetY);
+ t.setWindowCrop(leash, mCurrentDisplayBounds.width(), mCurrentDisplayBounds.height());
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
index 061d3f86b669..490ef3296be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
@@ -107,6 +107,7 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
transaction.setWindowCrop(leash, crop);
}
+ // TODO(b/173440321): Correct presentation of letterboxed activities in One-handed mode.
private void resolveTaskPositionAndCrop(
ActivityManager.RunningTaskInfo taskInfo,
Point positionInParent,
@@ -125,15 +126,18 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
final Rect activityBounds = taskInfo.letterboxActivityBounds;
Insets insets = getInsets();
+ Rect displayBoundsWithInsets =
+ new Rect(mWindowManager.getMaximumWindowMetrics().getBounds());
+ displayBoundsWithInsets.inset(insets);
Rect taskBoundsWithInsets = new Rect(taskBounds);
- applyInsets(taskBoundsWithInsets, insets, taskInfo.parentBounds);
+ taskBoundsWithInsets.intersect(displayBoundsWithInsets);
Rect activityBoundsWithInsets = new Rect(activityBounds);
- applyInsets(activityBoundsWithInsets, insets, taskInfo.parentBounds);
+ activityBoundsWithInsets.intersect(displayBoundsWithInsets);
Rect parentBoundsWithInsets = new Rect(parentBounds);
- applyInsets(parentBoundsWithInsets, insets, parentBounds);
+ parentBoundsWithInsets.intersect(displayBoundsWithInsets);
// Crop need to be in the task coordinates.
crop.set(activityBoundsWithInsets);
@@ -160,8 +164,6 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
switch (gravity) {
case Gravity.TOP:
positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top;
- // Showing status bar decor view.
- crop.top -= activityBoundsWithInsets.top - activityBounds.top;
break;
case Gravity.CENTER:
positionInParent.y +=
@@ -183,8 +185,6 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
final int gravity = mLetterboxConfigController.getLandscapeGravity();
// Align activity to the top.
positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top;
- // Showing status bar decor view.
- crop.top -= activityBoundsWithInsets.top - activityBounds.top;
switch (gravity) {
case Gravity.LEFT:
positionInParent.x += taskBoundsWithInsets.left - activityBoundsWithInsets.left;
@@ -205,6 +205,53 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
+ " for task: #" + taskInfo.taskId);
}
}
+
+ // New bounds of the activity after it's repositioned with required gravity.
+ Rect newActivityBounds = new Rect(activityBounds);
+ // Task's surfce will be repositioned to positionInParent together with the activity
+ // inside it so the new activity bounds are the original activity bounds offset by
+ // the task's offset.
+ newActivityBounds.offset(
+ positionInParent.x - taskBounds.left, positionInParent.y - taskBounds.top);
+ Rect newActivityBoundsWithInsets = new Rect(newActivityBounds);
+ newActivityBoundsWithInsets.intersect(displayBoundsWithInsets);
+ // Activity handles insets on its own (e.g. under status bar or navigation bar).
+ // crop that is calculated above crops all insets from an activity and below insets that
+ // can be shown are added back to the crop bounds (e.g. if activity is still shown at the
+ // top of the display then the top inset won't be cropped).
+ // After task's surface is repositioned, intersection between an activity and insets can
+ // change but if it doesn't, the activity should be shown under insets to maximize visible
+ // area.
+ // Also, an activity can use area under insets and insets shouldn't be cropped in this case
+ // regardless of a position on the screen.
+ final Rect activityInsetsFromCore = taskInfo.letterboxActivityInsets;
+ if (newActivityBounds.top - newActivityBoundsWithInsets.top
+ == activityBounds.top - activityBoundsWithInsets.top
+ // Check whether an activity is shown under inset. If it is, then the inset from
+ // WM Core and the inset computed here will be different because local insets
+ // doesn't take into account visibility of insets requested by the activity.
+ || activityBoundsWithInsets.top - activityBounds.top
+ != activityInsetsFromCore.top) {
+ crop.top -= activityBoundsWithInsets.top - activityBounds.top;
+ }
+ if (newActivityBounds.bottom - newActivityBoundsWithInsets.bottom
+ == activityBounds.bottom - activityBoundsWithInsets.bottom
+ || activityBounds.bottom - activityBoundsWithInsets.bottom
+ != activityInsetsFromCore.bottom) {
+ crop.bottom += activityBounds.bottom - activityBoundsWithInsets.bottom;
+ }
+ if (newActivityBounds.left - newActivityBoundsWithInsets.left
+ == activityBounds.left - activityBoundsWithInsets.left
+ || activityBoundsWithInsets.left - activityBounds.left
+ != activityInsetsFromCore.left) {
+ crop.left -= activityBoundsWithInsets.left - activityBounds.left;
+ }
+ if (newActivityBounds.right - newActivityBoundsWithInsets.right
+ == activityBounds.right - activityBoundsWithInsets.right
+ || activityBounds.right - activityBoundsWithInsets.right
+ != activityInsetsFromCore.right) {
+ crop.right += activityBounds.right - activityBoundsWithInsets.right;
+ }
}
private Insets getInsets() {
@@ -217,10 +264,4 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener {
| WindowInsets.Type.displayCutout());
}
- private void applyInsets(Rect innerBounds, Insets insets, Rect outerBounds) {
- Rect outerBoundsWithInsets = new Rect(outerBounds);
- outerBoundsWithInsets.inset(insets);
- innerBounds.intersect(outerBoundsWithInsets);
- }
-
}
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 9bb709f9a82a..821a00703adf 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.onehanded;
import androidx.annotation.NonNull;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import java.io.PrintWriter;
@@ -25,6 +26,7 @@ import java.io.PrintWriter;
/**
* Interface to engage one handed feature.
*/
+@ExternalThread
public interface OneHanded {
/**
* Return one handed settings enabled or not.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index 993e0e7ed016..d59aec2cc446 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -19,12 +19,15 @@ package com.android.wm.shell.pip;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
-import android.view.DisplayInfo;
-import android.view.IPinnedStackController;
+import android.os.RemoteException;
import android.view.IPinnedStackListener;
+import android.view.WindowManagerGlobal;
+
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
-import java.util.List;
/**
* PinnedStackListener that simply forwards all calls to each listener added via
@@ -32,8 +35,15 @@ import java.util.List;
* {@link com.android.server.wm.WindowManagerService#registerPinnedStackListener} replaces any
* previously set listener.
*/
-public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
- private List<PinnedStackListener> mListeners = new ArrayList<>();
+public class PinnedStackListenerForwarder {
+
+ private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl();
+ private final ShellExecutor mShellMainExecutor;
+ private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>();
+
+ public PinnedStackListenerForwarder(ShellExecutor shellMainExecutor) {
+ mShellMainExecutor = shellMainExecutor;
+ }
/** Adds a listener to receive updates from the WindowManagerService. */
public void addListener(PinnedStackListener listener) {
@@ -45,59 +55,76 @@ public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
mListeners.remove(listener);
}
- @Override
- public void onListenerRegistered(IPinnedStackController controller) {
- for (PinnedStackListener listener : mListeners) {
- listener.onListenerRegistered(controller);
- }
+ public void register(int displayId) throws RemoteException {
+ WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
+ displayId, mListenerImpl);
}
- @Override
- public void onMovementBoundsChanged(boolean fromImeAdjustment) {
+ private void onMovementBoundsChanged(boolean fromImeAdjustment) {
for (PinnedStackListener listener : mListeners) {
listener.onMovementBoundsChanged(fromImeAdjustment);
}
}
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ private void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
for (PinnedStackListener listener : mListeners) {
listener.onImeVisibilityChanged(imeVisible, imeHeight);
}
}
- @Override
- public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ private void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
for (PinnedStackListener listener : mListeners) {
listener.onActionsChanged(actions);
}
}
- @Override
- public void onActivityHidden(ComponentName componentName) {
+ private void onActivityHidden(ComponentName componentName) {
for (PinnedStackListener listener : mListeners) {
listener.onActivityHidden(componentName);
}
}
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+ private void onAspectRatioChanged(float aspectRatio) {
for (PinnedStackListener listener : mListeners) {
- listener.onDisplayInfoChanged(displayInfo);
+ listener.onAspectRatioChanged(aspectRatio);
}
}
- @Override
- public void onConfigurationChanged() {
- for (PinnedStackListener listener : mListeners) {
- listener.onConfigurationChanged();
+ @BinderThread
+ private class PinnedStackListenerImpl extends IPinnedStackListener.Stub {
+ @Override
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onMovementBoundsChanged(fromImeAdjustment);
+ });
}
- }
- @Override
- public void onAspectRatioChanged(float aspectRatio) {
- for (PinnedStackListener listener : mListeners) {
- listener.onAspectRatioChanged(aspectRatio);
+ @Override
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight);
+ });
+ }
+
+ @Override
+ public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onActionsChanged(actions);
+ });
+ }
+
+ @Override
+ public void onActivityHidden(ComponentName componentName) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onActivityHidden(componentName);
+ });
+ }
+
+ @Override
+ public void onAspectRatioChanged(float aspectRatio) {
+ mShellMainExecutor.execute(() -> {
+ PinnedStackListenerForwarder.this.onAspectRatioChanged(aspectRatio);
+ });
}
}
@@ -106,8 +133,6 @@ public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
* Subclasses can ignore those methods they do not intend to take action upon.
*/
public static class PinnedStackListener {
- public void onListenerRegistered(IPinnedStackController controller) {}
-
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
@@ -116,10 +141,6 @@ public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
public void onActivityHidden(ComponentName componentName) {}
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
-
- public void onConfigurationChanged() {}
-
public void onAspectRatioChanged(float aspectRatio) {}
}
}
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 9fa222ad4fdd..1f07542c9a27 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
@@ -17,12 +17,13 @@
package com.android.wm.shell.pip;
import android.annotation.Nullable;
-import android.app.ActivityManager;
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;
@@ -31,6 +32,7 @@ import java.util.function.Consumer;
/**
* Interface to engage picture in picture feature.
*/
+@ExternalThread
public interface Pip {
/**
* Closes PIP (PIPed activity and PIP system UI).
@@ -81,6 +83,12 @@ public interface Pip {
}
/**
+ * Called when configuration is changed.
+ */
+ default void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ /**
* Called when display size or font size of settings changed
*/
default void onDensityOrFontScaleChanged() {
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 d82946269ee8..fe018115408a 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
@@ -54,6 +54,7 @@ public class PipAnimationController {
public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
+ public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6;
@IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
TRANSITION_DIRECTION_NONE,
@@ -61,7 +62,8 @@ public class PipAnimationController {
TRANSITION_DIRECTION_TO_PIP,
TRANSITION_DIRECTION_LEAVE_PIP,
TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
- TRANSITION_DIRECTION_REMOVE_STACK
+ TRANSITION_DIRECTION_REMOVE_STACK,
+ TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
})
@Retention(RetentionPolicy.SOURCE)
public @interface TransitionDirection {}
@@ -109,13 +111,27 @@ public class PipAnimationController {
}
@SuppressWarnings("unchecked")
+ /**
+ * Construct and return an animator that animates from the {@param startBounds} to the
+ * {@param endBounds} with the given {@param direction}. If {@param direction} is type
+ * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate
+ * in a better, more smooth manner.
+ *
+ * In the case where one wants to start animation during an intermediate animation (for example,
+ * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate
+ * to the correct snap fraction region), then provide the base bounds, which is current PiP
+ * leash bounds before transformation/any animation. This is so when we try to construct
+ * the different transformation matrices for the animation, we are constructing this based off
+ * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
+ */
@VisibleForTesting
- public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
- Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) {
+ public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds,
+ Rect startBounds, Rect endBounds, Rect sourceHintRect,
+ @PipAnimationController.TransitionDirection int direction) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
+ PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds,
+ sourceHintRect, direction));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -130,8 +146,8 @@ public class PipAnimationController {
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect,
- direction));
+ PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds,
+ sourceHintRect, direction));
}
return mCurrentAnimator;
}
@@ -180,6 +196,7 @@ public class PipAnimationController {
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
+ private T mBaseValue;
protected T mCurrentValue;
protected T mStartValue;
private T mEndValue;
@@ -190,10 +207,11 @@ public class PipAnimationController {
private @TransitionDirection int mTransitionDirection;
private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
- Rect destinationBounds, T startValue, T endValue) {
+ Rect destinationBounds, T baseValue, T startValue, T endValue) {
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
+ mBaseValue = baseValue;
mStartValue = startValue;
mEndValue = endValue;
addListener(this);
@@ -263,6 +281,10 @@ public class PipAnimationController {
return mStartValue;
}
+ T getBaseValue() {
+ return mBaseValue;
+ }
+
@VisibleForTesting
public T getEndValue() {
return mEndValue;
@@ -334,7 +356,7 @@ public class PipAnimationController {
static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
Rect destinationBounds, float startValue, float endValue) {
return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
- destinationBounds, startValue, endValue) {
+ destinationBounds, startValue, startValue, endValue) {
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
@@ -367,7 +389,7 @@ public class PipAnimationController {
}
static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue, Rect sourceHintRect,
+ Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction) {
// Just for simplicity we'll interpolate between the source rect hint insets and empty
// insets to calculate the window crop
@@ -375,7 +397,7 @@ public class PipAnimationController {
if (isOutPipDirection(direction)) {
initialSourceValue = new Rect(endValue);
} else {
- initialSourceValue = new Rect(startValue);
+ initialSourceValue = new Rect(baseValue);
}
final Rect sourceHintRectInsets;
@@ -391,22 +413,24 @@ public class PipAnimationController {
// construct new Rect instances in case they are recycled
return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
- endValue, new Rect(startValue), new Rect(endValue)) {
+ endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
+ final Rect base = getBaseValue();
final Rect start = getStartValue();
final Rect end = getEndValue();
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
+
if (isOutPipDirection(direction)) {
getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+ getSurfaceTransactionHelper().scale(tx, leash, base, bounds);
}
} else {
final Rect insets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index 1bb5eda25058..484592e87a20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -102,9 +102,7 @@ public class PipBoundsAlgorithm {
return mSnapAlgorithm;
}
- /**
- * Responds to IPinnedStackListener on configuration change.
- */
+ /** Responds to configuration change. */
public void onConfigurationChanged(Context context) {
reloadResources(context);
}
@@ -125,15 +123,16 @@ public class PipBoundsAlgorithm {
/** Returns the destination bounds to place the PIP window on entry. */
public Rect getEntryDestinationBounds() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
- final boolean shouldRestoreReentryBounds = reentryState != null;
- final Rect destinationBounds = shouldRestoreReentryBounds
+ final Rect destinationBounds = reentryState != null
? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize())
: getDefaultBounds();
- return transformBoundsToAspectRatioIfValid(destinationBounds,
+ final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
+ final Rect r = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
- shouldRestoreReentryBounds);
+ useCurrentSize);
+ return r;
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@@ -223,24 +222,36 @@ public class PipBoundsAlgorithm {
private Rect getDefaultBounds(float snapFraction, Size size) {
final Rect defaultBounds = new Rect();
if (snapFraction != INVALID_SNAP_FRACTION && size != null) {
+ // The default bounds are the given size positioned at the given snap fraction.
defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
final Rect movementBounds = getMovementBounds(defaultBounds);
mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
+ return defaultBounds;
+ }
+
+ // Calculate the default size.
+ final Size defaultSize;
+ final Rect insetBounds = new Rect();
+ getInsetBounds(insetBounds);
+ final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
+ final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
+ if (overrideMinSize != null) {
+ // The override minimal size is set, use that as the default size making sure it's
+ // adjusted to the aspect ratio.
+ defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
+ } else {
+ // Calculate the default size using the display size and default min edge size.
+ defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
+ mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
+ }
+
+ // Now that we have the default size, apply the snap fraction if valid or position the
+ // bounds using the default gravity.
+ if (snapFraction != INVALID_SNAP_FRACTION) {
+ defaultBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+ final Rect movementBounds = getMovementBounds(defaultBounds);
+ mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
} else {
- final Rect insetBounds = new Rect();
- getInsetBounds(insetBounds);
- final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
- final Size defaultSize;
- final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
- if (overrideMinSize != null) {
- // The override minimal size is set, use that as the default size making sure it's
- // adjusted to the aspect ratio.
- defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
- } else {
- // Calculate the default size using the display size and default min edge size.
- defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
- mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
- }
Gravity.apply(mDefaultStackGravity, defaultSize.getWidth(), defaultSize.getHeight(),
insetBounds, 0, Math.max(
mPipBoundsState.isImeShowing() ? mPipBoundsState.getImeHeight() : 0,
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 53aa61477483..4493d38d2144 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
@@ -76,6 +76,8 @@ public final class PipBoundsState {
private int mImeHeight;
private boolean mIsShelfShowing;
private int mShelfHeight;
+ /** Whether the user has resized the PIP manually. */
+ private boolean mHasUserResizedPip;
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable BiConsumer<Boolean, Integer> mOnShelfVisibilityChangeCallback;
@@ -189,8 +191,8 @@ public final class PipBoundsState {
}
/** Save the reentry state to restore to when re-entering PIP mode. */
- public void saveReentryState(@NonNull Rect bounds, float fraction) {
- mPipReentryState = new PipReentryState(new Size(bounds.width(), bounds.height()), fraction);
+ public void saveReentryState(Size size, float fraction) {
+ mPipReentryState = new PipReentryState(size, fraction);
}
/** Returns the saved reentry state. */
@@ -205,6 +207,7 @@ public final class PipBoundsState {
mLastPipComponentName = lastPipComponentName;
if (changed) {
clearReentryState();
+ setHasUserResizedPip(false);
}
}
@@ -329,6 +332,16 @@ public final class PipBoundsState {
return mShelfHeight;
}
+ /** Returns whether the user has resized the PIP. */
+ public boolean hasUserResizedPip() {
+ return mHasUserResizedPip;
+ }
+
+ /** Set whether the user has resized the PIP. */
+ public void setHasUserResizedPip(boolean hasUserResizedPip) {
+ mHasUserResizedPip = hasUserResizedPip;
+ }
+
/**
* Registers a callback when the minimal size of PIP that is set by the app changes.
*/
@@ -397,15 +410,15 @@ public final class PipBoundsState {
static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
- private final @NonNull Size mSize;
+ private final @Nullable Size mSize;
private final float mSnapFraction;
- PipReentryState(@NonNull Size size, float snapFraction) {
+ PipReentryState(@Nullable Size size, float snapFraction) {
mSize = size;
mSnapFraction = snapFraction;
}
- @NonNull
+ @Nullable
Size getSize() {
return mSize;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
new file mode 100644
index 000000000000..8d9ad4d1b96c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -0,0 +1,106 @@
+/*
+ * 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;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.RemoteAction;
+import android.content.pm.ParceledListSlice;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * Interface to allow {@link com.android.wm.shell.pip.PipTaskOrganizer} to call into
+ * PiP menu when certain events happen (task appear/vanish, PiP move, etc.)
+ */
+public interface PipMenuController {
+
+ String MENU_WINDOW_TITLE = "PipMenuView";
+
+ /**
+ * Called when
+ * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
+ * is called.
+ */
+ void attach(SurfaceControl leash);
+
+ /**
+ * Called when
+ * {@link PipTaskOrganizer#onTaskVanished(RunningTaskInfo)} is called.
+ */
+ void detach();
+
+ /**
+ * Check if menu is visible or not.
+ */
+ boolean isMenuVisible();
+
+ /**
+ * Show the PIP menu.
+ */
+ void showMenu();
+
+ /**
+ * Given a set of actions, update the menu.
+ */
+ void setAppActions(ParceledListSlice<RemoteAction> appActions);
+
+ /**
+ * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
+ * need to synchronize the movements on the same frame as PiP.
+ */
+ default void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {}
+
+ /**
+ * Move the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
+ * need to synchronize the movements on the same frame as PiP.
+ */
+ default void movePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {}
+
+ /**
+ * Update the PiP menu with the given bounds for re-layout purposes.
+ */
+ default void updateMenuBounds(Rect destinationBounds) {}
+
+ /**
+ * Returns a default LayoutParams for the PIP Menu.
+ * @param width the PIP stack width.
+ * @param height the PIP stack height.
+ */
+ default WindowManager.LayoutParams getPipMenuLayoutParams(String title, int width, int height) {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
+ lp.setTitle(title);
+ return lp;
+ }
+}
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 7cc2a419354e..9081783eeab8 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
@@ -30,6 +30,7 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
@@ -63,7 +64,6 @@ import com.android.internal.os.SomeArgs;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.phone.PipMenuActivityController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipUpdateThread;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -135,8 +135,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Handler mUpdateHandler;
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
- // TODO(b/172286265): Remove dependency on .pip.PHONE.PipMenuActivityController
- private final PipMenuActivityController mMenuActivityController;
+ private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
@@ -264,7 +263,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipBoundsAlgorithm boundsHandler,
- PipMenuActivityController menuActivityController,
+ @NonNull PipMenuController pipMenuController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional,
@NonNull DisplayController displayController,
@@ -274,7 +273,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = boundsHandler;
- mMenuActivityController = menuActivityController;
+ mPipMenuController = pipMenuController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = surfaceTransactionHelper;
@@ -501,9 +500,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(info.displayId);
}
- if (mMenuActivityController != null) {
- mMenuActivityController.onTaskAppeared();
- }
+ mPipMenuController.attach(leash);
if (mShouldIgnoreEnteringPipTransition) {
final Rect destinationBounds = mPipBoundsState.getBounds();
@@ -674,9 +671,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPictureInPictureParams = null;
mState = State.UNDEFINED;
mPipUiEventLoggerLogger.setTaskInfo(null);
- if (mMenuActivityController != null) {
- mMenuActivityController.onTaskVanished();
- }
+ mPipMenuController.detach();
}
@Override
@@ -819,6 +814,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
+ /**
+ * Animates resizing of the pinned stack given the duration and start bounds.
+ * This is used when the starting bounds is not the current PiP bounds.
+ */
+ public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+ Consumer<Rect> updateBoundsCallback) {
+ if (mShouldDeferEnteringPip) {
+ Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
+ return;
+ }
+ scheduleAnimateResizePip(fromBounds, toBounds, null /* sourceHintRect */,
+ TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback);
+ }
+
private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
int durationMs, Consumer<Rect> updateBoundsCallback) {
@@ -956,9 +965,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
.round(tx, mLeash, mState.isInPip());
- if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+ if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
- mMenuActivityController.resizePipMenu(mLeash, tx, destinationBounds));
+ mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
@@ -982,9 +991,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
- if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+ if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
- mMenuActivityController.movePipMenu(mLeash, tx, destinationBounds));
+ mPipMenuController.movePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
@@ -1001,8 +1010,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
return;
- } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA
- && mMenuActivityController != null) {
+ } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
// TODO: Synchronize this correctly in #applyEnterPipSyncTransaction
finishResizeForMenu(destinationBounds);
return;
@@ -1015,13 +1023,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void finishResizeForMenu(Rect destinationBounds) {
- if (mMenuActivityController == null) {
- if (DEBUG) Log.d(TAG, "mMenuActivityController is null");
- return;
- }
runOnMainHandler(() -> {
- mMenuActivityController.movePipMenu(null, null, destinationBounds);
- mMenuActivityController.updateMenuBounds(destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.updateMenuBounds(destinationBounds);
});
}
@@ -1083,8 +1087,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Log.w(TAG, "Abort animation, invalid leash");
return;
}
+ Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
+ ? mPipBoundsState.getBounds() : currentBounds;
mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect, direction)
+ .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect,
+ direction)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 1d5430008501..5db8f3d7ef40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -18,12 +18,6 @@ package com.android.wm.shell.pip.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.annotation.Nullable;
@@ -33,7 +27,6 @@ import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Debug;
@@ -44,27 +37,26 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.pip.PipMenuController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
- * Manages the PiP menu activity which can show menu options or a scrim.
+ * Manages the PiP menu view which can show menu options or a scrim.
*
* The current media session provides actions whenever there are no valid actions provided by the
* current PiP activity. Otherwise, those actions always take precedence.
*/
-public class PipMenuActivityController {
+public class PhonePipMenuController implements PipMenuController {
private static final String TAG = "PipMenuActController";
- private static final String MENU_WINDOW_TITLE = "PipMenuView";
private static final boolean DEBUG = false;
public static final int MENU_STATE_NONE = 0;
@@ -124,7 +116,7 @@ public class PipMenuActivityController {
}
};
- public PipMenuActivityController(Context context,
+ public PhonePipMenuController(Context context,
PipMediaController mediaController, SystemWindows systemWindows) {
mContext = context;
mMediaController = mediaController;
@@ -138,20 +130,22 @@ public class PipMenuActivityController {
/**
* Attach the menu when the PiP task first appears.
*/
- public void onTaskAppeared() {
+ @Override
+ public void attach(SurfaceControl leash) {
attachPipMenuView();
}
/**
* Detach the menu when the PiP task is gone.
*/
- public void onTaskVanished() {
+ @Override
+ public void detach() {
hideMenu();
detachPipMenuView();
}
- public void onPinnedStackAnimationEnded() {
+ void onPinnedStackAnimationEnded() {
if (isMenuVisible()) {
mPipMenuView.onPipAnimationEnded();
}
@@ -163,7 +157,9 @@ public class PipMenuActivityController {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this);
- mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(0, 0), 0, SHELL_ROOT_LAYER_PIP);
+ mSystemWindows.addView(mPipMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
}
private void detachPipMenuView() {
@@ -181,9 +177,11 @@ public class PipMenuActivityController {
* Updates the layout parameters of the menu.
* @param destinationBounds New Menu bounds.
*/
+ @Override
public void updateMenuBounds(Rect destinationBounds) {
mSystemWindows.updateViewLayout(mPipMenuView,
- getPipMenuLayoutParams(destinationBounds.width(), destinationBounds.height()));
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, destinationBounds.width(),
+ destinationBounds.height()));
}
/**
@@ -206,6 +204,16 @@ public class PipMenuActivityController {
}
/**
+ * When other components requests the menu controller directly to show the menu, we must
+ * first fire off the request to the other listeners who will then propagate the call
+ * back to the controller with the right parameters.
+ */
+ @Override
+ public void showMenu() {
+ mListeners.forEach(Listener::onPipShowMenu);
+ }
+
+ /**
* Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
* upon PiP window transition is finished.
*/
@@ -239,7 +247,9 @@ public class PipMenuActivityController {
+ " callers=\n" + Debug.getCallers(5, " "));
}
- maybeCreateSyncApplier();
+ if (!maybeCreateSyncApplier()) {
+ return;
+ }
mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
showResizeHandle);
@@ -248,6 +258,7 @@ public class PipMenuActivityController {
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
+ @Override
public void movePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
@@ -288,6 +299,7 @@ public class PipMenuActivityController {
/**
* Does an immediate window crop of the PiP menu.
*/
+ @Override
public void resizePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
@@ -389,8 +401,9 @@ public class PipMenuActivityController {
}
/**
- * Sets the menu actions to the actions provided by the current PiP activity.
+ * Sets the menu actions to the actions provided by the current PiP menu.
*/
+ @Override
public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
mAppActions = appActions;
updateMenuActions();
@@ -404,10 +417,6 @@ public class PipMenuActivityController {
mListeners.forEach(Listener::onPipDismiss);
}
- void onPipShowMenu() {
- mListeners.forEach(Listener::onPipShowMenu);
- }
-
/**
* @return the best set of actions to show in the PiP menu.
*/
@@ -419,21 +428,6 @@ public class PipMenuActivityController {
}
/**
- * Returns a default LayoutParams for the PIP Menu.
- * @param width the PIP stack width.
- * @param height the PIP stack height.
- */
- public static WindowManager.LayoutParams getPipMenuLayoutParams(int width, int height) {
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
- TYPE_APPLICATION_OVERLAY,
- FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT);
- lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
- lp.setTitle(MENU_WINDOW_TITLE);
- return lp;
- }
-
- /**
* Updates the PiP menu with the best set of actions provided.
*/
private void updateMenuActions() {
@@ -519,7 +513,7 @@ public class PipMenuActivityController {
}
}
- public void dump(PrintWriter pw, String prefix) {
+ void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mMenuState=" + mMenuState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index f153aa5a1beb..7194fc70025c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -20,15 +20,18 @@ import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.view.MagnificationSpec;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import androidx.annotation.BinderThread;
+
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -40,8 +43,7 @@ import java.util.List;
* Expose the touch actions to accessibility as if this object were a window with a single view.
* That pseudo-view exposes all of the actions this object can perform.
*/
-public class PipAccessibilityInteractionConnection
- extends IAccessibilityInteractionConnection.Stub {
+public class PipAccessibilityInteractionConnection {
public interface AccessibilityCallbacks {
void onAccessibilityShowMenu();
@@ -50,14 +52,15 @@ public class PipAccessibilityInteractionConnection
private static final long ACCESSIBILITY_NODE_ID = 1;
private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList;
- private Context mContext;
- private Handler mHandler;
+ private final Context mContext;
+ private final ShellExecutor mShellMainExcutor;
private final @NonNull PipBoundsState mPipBoundsState;
- private PipMotionHelper mMotionHelper;
- private PipTaskOrganizer mTaskOrganizer;
- private PipSnapAlgorithm mSnapAlgorithm;
- private Runnable mUpdateMovementBoundCallback;
- private AccessibilityCallbacks mCallbacks;
+ private final PipMotionHelper mMotionHelper;
+ private final PipTaskOrganizer mTaskOrganizer;
+ private final PipSnapAlgorithm mSnapAlgorithm;
+ private final Runnable mUpdateMovementBoundCallback;
+ private final AccessibilityCallbacks mCallbacks;
+ private final IAccessibilityInteractionConnection mConnectionImpl;
private final Rect mNormalBounds = new Rect();
private final Rect mExpandedBounds = new Rect();
@@ -69,19 +72,23 @@ public class PipAccessibilityInteractionConnection
@NonNull PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
- Handler handler) {
+ ShellExecutor shellMainExcutor) {
mContext = context;
- mHandler = handler;
+ mShellMainExcutor = shellMainExcutor;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mTaskOrganizer = taskOrganizer;
mSnapAlgorithm = snapAlgorithm;
mUpdateMovementBoundCallback = updateMovementBoundCallback;
mCallbacks = callbacks;
+ mConnectionImpl = new PipAccessibilityInteractionConnectionImpl();
+ }
+
+ public void register(AccessibilityManager am) {
+ am.setPictureInPictureActionReplacingConnection(mConnectionImpl);
}
- @Override
- public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+ private void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) {
@@ -94,8 +101,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void performAccessibilityAction(long accessibilityNodeId, int action,
+ private void performAccessibilityAction(long accessibilityNodeId, int action,
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
@@ -115,9 +121,7 @@ public class PipAccessibilityInteractionConnection
} else {
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK:
- mHandler.post(() -> {
- mCallbacks.onAccessibilityShowMenu();
- });
+ mCallbacks.onAccessibilityShowMenu();
result = true;
break;
case AccessibilityNodeInfo.ACTION_DISMISS:
@@ -172,8 +176,7 @@ public class PipAccessibilityInteractionConnection
});
}
- @Override
- public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ private void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
String viewId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
@@ -185,8 +188,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ private void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
@@ -198,8 +200,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
+ private void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
// We have no view that can take focus
@@ -210,8 +211,7 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
+ private void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
// We have no view that can take focus
@@ -222,16 +222,6 @@ public class PipAccessibilityInteractionConnection
}
}
- @Override
- public void clearAccessibilityFocus() {
- // We should not be here.
- }
-
- @Override
- public void notifyOutsideTouch() {
- // Do nothing.
- }
-
/**
* Update the normal and expanded bounds so they can be used for Resize.
*/
@@ -271,4 +261,95 @@ public class PipAccessibilityInteractionConnection
mAccessibilityNodeInfoList.add(info);
return mAccessibilityNodeInfoList;
}
+
+ @BinderThread
+ private class PipAccessibilityInteractionConnectionImpl
+ extends IAccessibilityInteractionConnection.Stub {
+ @Override
+ public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec,
+ Bundle arguments) throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this
+ .findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, bounds,
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec, arguments);
+ });
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByViewId(
+ accessibilityNodeId, viewId, bounds, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid, spec);
+ });
+ }
+
+ @Override
+ public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
+ Region bounds, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findAccessibilityNodeInfosByText(
+ accessibilityNodeId, text, bounds, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid, spec);
+ });
+ }
+
+ @Override
+ public void findFocus(long accessibilityNodeId, int focusType, Region bounds,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.findFocus(accessibilityNodeId, focusType,
+ bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ });
+ }
+
+ @Override
+ public void focusSearch(long accessibilityNodeId, int direction, Region bounds,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec)
+ throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.focusSearch(accessibilityNodeId,
+ direction,
+ bounds, interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
+ });
+ }
+
+ @Override
+ public void performAccessibilityAction(long accessibilityNodeId, int action,
+ Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid) throws RemoteException {
+ mShellMainExcutor.execute(() -> {
+ PipAccessibilityInteractionConnection.this.performAccessibilityAction(
+ accessibilityNodeId, action, arguments, interactionId, callback, flags,
+ interrogatingPid, interrogatingTid);
+ });
+ }
+
+ @Override
+ public void clearAccessibilityFocus() throws RemoteException {
+ // Do nothing
+ }
+
+ @Override
+ public void notifyOutsideTouch() throws RemoteException {
+ // Do nothing
+ }
+ }
}
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 598b5d9b5d30..4d2760259521 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
@@ -33,15 +33,16 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.Pair;
+import android.util.Size;
import android.util.Slog;
import android.view.DisplayInfo;
-import android.view.IPinnedStackController;
import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
@@ -52,7 +53,6 @@ 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.PipInputConsumer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -87,12 +87,11 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private final Rect mTmpInsetBounds = new Rect();
- protected final Rect mReentryBounds = new Rect();
private boolean mIsInFixedRotation;
private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
- protected PipMenuActivityController mMenuController;
+ protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
protected PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
new PipControllerPinnedStackListener();
@@ -106,6 +105,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
// Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update
// the display layout in the bounds handler in this case.
onDisplayRotationChangedNotInPip(mContext, toRotation);
+ // do not forget to update the movement bounds as well.
+ updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */,
+ false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
return;
}
// If there is an animation running (ie. from a shelf offset), then ensure that we calculate
@@ -138,7 +140,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
};
- private DisplayController.OnDisplaysChangedListener mFixedRotationListener =
+ private final DisplayController.OnDisplaysChangedListener mFixedRotationListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
public void onFixedRotationStarted(int displayId, int newRotation) {
@@ -163,63 +165,38 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private class PipControllerPinnedStackListener extends
PinnedStackListenerForwarder.PinnedStackListener {
@Override
- public void onListenerRegistered(IPinnedStackController controller) {
- mMainExecutor.execute(() -> mTouchHandler.setPinnedStackController(controller));
- }
-
- @Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mMainExecutor.execute(() -> {
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
- });
+ mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+ mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mMainExecutor.execute(() -> updateMovementBounds(null /* toBounds */,
+ updateMovementBounds(null /* toBounds */,
false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */));
+ null /* windowContainerTransaction */);
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
- mMainExecutor.execute(() -> mMenuController.setAppActions(actions));
+ mMenuController.setAppActions(actions);
}
@Override
public void onActivityHidden(ComponentName componentName) {
- mMainExecutor.execute(() -> {
- if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
- // The activity was removed, we don't want to restore to the reentry state
- // saved for this component anymore.
- mPipBoundsState.setLastPipComponentName(null);
- }
- });
- }
-
- @Override
- public void onDisplayInfoChanged(DisplayInfo displayInfo) {
- mMainExecutor.execute(() -> mPipBoundsState.setDisplayInfo(displayInfo));
- }
-
- @Override
- public void onConfigurationChanged() {
- mMainExecutor.execute(() -> {
- mPipBoundsAlgorithm.onConfigurationChanged(mContext);
- mTouchHandler.onConfigurationChanged();
- mPipBoundsState.onConfigurationChanged();
- });
+ if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
+ // The activity was removed, we don't want to restore to the reentry state
+ // saved for this component anymore.
+ mPipBoundsState.setLastPipComponentName(null);
+ }
}
@Override
public void onAspectRatioChanged(float aspectRatio) {
// TODO(b/169373982): Remove this callback as it is redundant with PipTaskOrg params
// change.
- mMainExecutor.execute(() -> {
- mPipBoundsState.setAspectRatio(aspectRatio);
- mTouchHandler.onAspectRatioChanged();
- });
+ mPipBoundsState.setAspectRatio(aspectRatio);
+ mTouchHandler.onAspectRatioChanged();
}
}
@@ -229,7 +206,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipMediaController pipMediaController,
- PipMenuActivityController pipMenuActivityController,
+ PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -250,7 +227,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
- mMenuController = pipMenuActivityController;
+ mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
@@ -349,6 +326,15 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ mTouchHandler.onConfigurationChanged();
+ mPipBoundsState.onConfigurationChanged();
+ });
+ }
+
+ @Override
public void onDensityOrFontScaleChanged() {
mMainExecutor.execute(() -> {
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
@@ -452,10 +438,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
@Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) {
- // Exiting PIP, save the reentry bounds to restore to when re-entering.
- updateReentryBounds(pipBounds);
- final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mReentryBounds);
- mPipBoundsState.saveReentryState(mReentryBounds, snapFraction);
+ // Exiting PIP, save the reentry state to restore to when re-entering.
+ saveReentryState(pipBounds);
}
// Disable touches while the animation is running
mTouchHandler.setTouchEnabled(false);
@@ -464,14 +448,16 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
- /**
- * Update the bounds used to save the re-entry size and snap fraction when exiting PIP.
- */
- public void updateReentryBounds(Rect bounds) {
- final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
- float snapFraction = mPipBoundsAlgorithm.getSnapFraction(bounds);
- mPipBoundsAlgorithm.applySnapFraction(reentryBounds, snapFraction);
- mReentryBounds.set(reentryBounds);
+ /** Save the state to restore to on re-entry. */
+ public void saveReentryState(Rect pipBounds) {
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
+ if (mPipBoundsState.hasUserResizedPip()) {
+ final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
+ final Size reentrySize = new Size(reentryBounds.width(), reentryBounds.height());
+ mPipBoundsState.saveReentryState(reentrySize, snapFraction);
+ } else {
+ mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
+ }
}
/**
@@ -547,7 +533,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
*
* @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
*/
- public boolean onDisplayRotationChanged(Context context, Rect outBounds, Rect oldBounds,
+ private boolean onDisplayRotationChanged(Context context, Rect outBounds, Rect oldBounds,
Rect outInsetBounds,
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current {@link #mDisplayInfo}
@@ -632,7 +618,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
public static PipController create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
- PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer,
+ PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -641,7 +627,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer,
+ pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 87ddb181dcce..0c64c8ca9b64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell.common;
+package com.android.wm.shell.pip.phone;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
-import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import android.os.Binder;
import android.os.IBinder;
@@ -30,7 +28,6 @@ import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
-import android.view.WindowManagerGlobal;
import java.io.PrintWriter;
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 6e3cd8e9aa09..2e10fc93cafb 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
@@ -23,9 +23,9 @@ import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTR
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -105,7 +105,7 @@ public class PipMenuView extends FrameLayout {
private int mBetweenActionPaddingLand;
private AnimatorSet mMenuContainerAnimator;
- private PipMenuActivityController mController;
+ private PhonePipMenuController mController;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -127,7 +127,7 @@ public class PipMenuView extends FrameLayout {
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
- public PipMenuView(Context context, PipMenuActivityController controller) {
+ public PipMenuView(Context context, PhonePipMenuController controller) {
super(context, null, 0);
mContext = context;
mController = controller;
@@ -182,7 +182,7 @@ public class PipMenuView extends FrameLayout {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
- mController.onPipShowMenu();
+ mController.showMenu();
}
return super.performAccessibilityAction(host, action, args);
}
@@ -475,7 +475,7 @@ public class PipMenuView extends FrameLayout {
final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second));
}
}
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 903f7d773896..e32d3b955081 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
@@ -70,7 +70,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private final PipTaskOrganizer mPipTaskOrganizer;
private @NonNull PipBoundsState mPipBoundsState;
- private PipMenuActivityController mMenuController;
+ private PhonePipMenuController mMenuController;
private PipSnapAlgorithm mSnapAlgorithm;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -162,7 +162,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
};
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
- PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
+ PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
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
new file mode 100644
index 000000000000..28cbe35745a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
@@ -0,0 +1,123 @@
+/*
+ * 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.phone;
+
+import android.graphics.Point;
+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();
+ /**
+ * 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.
+ */
+ 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));
+
+ 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)));
+ }
+ }
+
+ // 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;
+ }
+
+ TMP_RECT.set(currentPipBounds.centerX() - width / 2,
+ currentPipBounds.centerY() - height / 2,
+ currentPipBounds.centerX() + width / 2,
+ currentPipBounds.centerY() + height / 2);
+ return TMP_RECT;
+ }
+}
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 e5a49c430d82..02f6231c6ecf 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
@@ -39,7 +39,6 @@ import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
@@ -62,19 +61,21 @@ import java.util.function.Function;
public class PipResizeGestureHandler {
private static final String TAG = "PipResizeGestureHandler";
- private static final float PINCH_THRESHOLD = 0.05f;
- private static final float STARTING_SCALE_FACTOR = 1.0f;
+ private static final int PINCH_RESIZE_SNAP_DURATION = 250;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipMotionHelper mMotionHelper;
private final PipBoundsState mPipBoundsState;
+ private final PipTaskOrganizer mPipTaskOrganizer;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipUiEventLogger mPipUiEventLogger;
private final int mDisplayId;
private final Executor mMainExecutor;
- private final ScaleGestureDetector mScaleGestureDetector;
private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
+ private final PointF mDownSecondaryPoint = new PointF();
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final Rect mLastResizeBounds = new Rect();
@@ -88,23 +89,27 @@ public class PipResizeGestureHandler {
private final Rect mDisplayBounds = new Rect();
private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
+ private final Handler mHandler;
private int mDelta;
private float mTouchSlop;
+
private boolean mAllowGesture;
private boolean mIsAttached;
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 = false;
- private float mScaleFactor = STARTING_SCALE_FACTOR;
+ int mFirstIndex = -1;
+ int mSecondIndex = -1;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
- private PipTaskOrganizer mPipTaskOrganizer;
- private PipMenuActivityController mPipMenuActivityController;
- private PipUiEventLogger mPipUiEventLogger;
private int mCtrlType;
@@ -112,7 +117,7 @@ public class PipResizeGestureHandler {
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
- PipMenuActivityController menuActivityController) {
+ PhonePipMenuController menuActivityController) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
@@ -122,68 +127,13 @@ public class PipResizeGestureHandler {
mPipTaskOrganizer = pipTaskOrganizer;
mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- mPipMenuActivityController = menuActivityController;
+ mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
+ mHandler = new Handler(Looper.getMainLooper());
context.getDisplay().getRealSize(mMaxSize);
reloadResources();
- mScaleGestureDetector = new ScaleGestureDetector(context,
- new ScaleGestureDetector.OnScaleGestureListener() {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- mScaleFactor *= detector.getScaleFactor();
-
- if (!mThresholdCrossed
- && (mScaleFactor > (STARTING_SCALE_FACTOR + PINCH_THRESHOLD)
- || mScaleFactor < (STARTING_SCALE_FACTOR - PINCH_THRESHOLD))) {
- mThresholdCrossed = true;
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- int height = Math.min(mMaxSize.y, Math.max(mMinSize.y,
- (int) (mScaleFactor * mLastDownBounds.height())));
- int width = Math.min(mMaxSize.x, Math.max(mMinSize.x,
- (int) (mScaleFactor * mLastDownBounds.width())));
- int top, bottom, left, right;
-
- if ((mCtrlType & CTRL_TOP) != 0) {
- top = mLastDownBounds.bottom - height;
- bottom = mLastDownBounds.bottom;
- } else {
- top = mLastDownBounds.top;
- bottom = mLastDownBounds.top + height;
- }
-
- if ((mCtrlType & CTRL_LEFT) != 0) {
- left = mLastDownBounds.right - width;
- right = mLastDownBounds.right;
- } else {
- left = mLastDownBounds.left;
- right = mLastDownBounds.left + width;
- }
-
- mLastResizeBounds.set(left, top, right, bottom);
- mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds,
- mLastResizeBounds,
- null);
- }
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- setCtrlTypeForPinchToZoom();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mScaleFactor = STARTING_SCALE_FACTOR;
- finishResize();
- }
- });
-
mEnablePinchResize = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_PINCH_RESIZE,
@@ -274,7 +224,7 @@ public class PipResizeGestureHandler {
if (ev instanceof MotionEvent) {
if (mUsingPinchToZoom) {
- mScaleGestureDetector.onTouchEvent((MotionEvent) ev);
+ onPinchResize((MotionEvent) ev);
} else {
onDragCornerResize((MotionEvent) ev);
}
@@ -282,6 +232,13 @@ 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;
+ }
+
+ /**
* Check whether the current x,y coordinate is within the region in which drag-resize should
* start.
* This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
@@ -295,7 +252,7 @@ public class PipResizeGestureHandler {
* |_|_|_________|_|_|
* |_|_| |_|_|
*/
- public boolean isWithinTouchRegion(int x, int y) {
+ public boolean isWithinDragResizeRegion(int x, int y) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
if (currentPipBounds == null) {
return false;
@@ -327,15 +284,14 @@ public class PipResizeGestureHandler {
if (isInValidSysUiState()) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- // Always pass the DOWN event to the ScaleGestureDetector
- mScaleGestureDetector.onTouchEvent(ev);
- if (isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) {
+ if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
return true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
mUsingPinchToZoom = true;
return true;
}
@@ -348,33 +304,11 @@ public class PipResizeGestureHandler {
return false;
}
- private void setCtrlTypeForPinchToZoom() {
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- mLastDownBounds.set(mPipBoundsState.getBounds());
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
-
- if (currentPipBounds.left == mDisplayBounds.left) {
- mCtrlType |= CTRL_RIGHT;
- } else {
- mCtrlType |= CTRL_LEFT;
- }
-
- if (currentPipBounds.top > mDisplayBounds.top + mDisplayBounds.height()) {
- mCtrlType |= CTRL_TOP;
- } else {
- mCtrlType |= CTRL_BOTTOM;
- }
- }
-
private void setCtrlType(int x, int y) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+
mDisplayBounds.set(movementBounds.left,
movementBounds.top,
movementBounds.right + currentPipBounds.width(),
@@ -408,6 +342,79 @@ public class PipResizeGestureHandler {
return mIsSysUiStateValid;
}
+ private void onPinchResize(MotionEvent ev) {
+ int action = ev.getActionMasked();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mFirstIndex = -1;
+ mSecondIndex = -1;
+ finishResize();
+ }
+
+ if (ev.getPointerCount() != 2) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mFirstIndex == -1 && mSecondIndex == -1) {
+ mFirstIndex = 0;
+ mSecondIndex = 1;
+ mLastResizeBounds.setEmpty();
+ mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
+ mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+
+ mLastResizeBounds.setEmpty();
+ mLastDownBounds.set(mPipBoundsState.getBounds());
+ }
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mFirstIndex == -1 || mSecondIndex == -1) {
+ return;
+ }
+
+ float x0 = ev.getRawX(mFirstIndex);
+ float y0 = ev.getRawY(mFirstIndex);
+ float x1 = ev.getRawX(mSecondIndex);
+ float y1 = ev.getRawY(mSecondIndex);
+
+ 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) {
+ mInputMonitor.pilferPointers();
+ mThresholdCrossed1 = true;
+ // Reset the down to begin resizing from this point
+ mDownSecondaryPoint.set(x1, y1);
+ }
+ if (mThresholdCrossed0 || mThresholdCrossed1) {
+ 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 currentPipBounds = mPipBoundsState.getBounds();
+ mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1,
+ mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y,
+ currentPipBounds, mMinSize.x, mMinSize.y, mMaxSize));
+
+ mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
+ null);
+ mPipBoundsState.setHasUserResizedPip(true);
+ }
+ }
+ }
+
private void onDragCornerResize(MotionEvent ev) {
int action = ev.getActionMasked();
float x = ev.getX();
@@ -415,15 +422,15 @@ public class PipResizeGestureHandler {
if (action == MotionEvent.ACTION_DOWN) {
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinTouchRegion((int) x, (int) y);
+ mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
if (mAllowGesture) {
setCtrlType((int) x, (int) y);
mDownPoint.set(x, y);
mLastDownBounds.set(mPipBoundsState.getBounds());
}
if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY())
- && mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenu();
+ && mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
}
} else if (mAllowGesture) {
@@ -442,9 +449,9 @@ public class PipResizeGestureHandler {
mInputMonitor.pilferPointers();
}
if (mThresholdCrossed) {
- if (mPipMenuActivityController.isMenuVisible()) {
- mPipMenuActivityController.hideMenuWithoutResize();
- mPipMenuActivityController.hideMenu();
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenuWithoutResize();
+ mPhonePipMenuController.hideMenu();
}
final Rect currentPipBounds = mPipBoundsState.getBounds();
mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
@@ -456,6 +463,7 @@ public class PipResizeGestureHandler {
true /* useCurrentSize */);
mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds,
null);
+ mPipBoundsState.setHasUserResizedPip(true);
}
break;
case MotionEvent.ACTION_UP:
@@ -468,15 +476,30 @@ public class PipResizeGestureHandler {
private void finishResize() {
if (!mLastResizeBounds.isEmpty()) {
- mUserResizeBounds.set(mLastResizeBounds);
- mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- (Rect bounds) -> {
- new Handler(Looper.getMainLooper()).post(() -> {
- mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
- resetState();
+ final Runnable callback = () -> {
+ mUserResizeBounds.set(mLastResizeBounds);
+ mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
+
+ // 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) {
+ final Rect startBounds = new Rect(mLastResizeBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
+ mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+ mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
+ PINCH_RESIZE_SNAP_DURATION,
+ (Rect rect) -> {
+ mHandler.post(callback);
});
- });
+ } else {
+ mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
+ (Rect bounds) -> {
+ mHandler.post(callback);
+ });
+ }
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
} else {
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 a78c4ecdb39f..33439a412b4d 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
@@ -18,9 +18,9 @@ package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -31,11 +31,8 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.RemoteException;
import android.provider.DeviceConfig;
-import android.util.Log;
import android.util.Size;
-import android.view.IPinnedStackController;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -47,6 +44,7 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -75,10 +73,9 @@ public class PipTouchHandler {
private final PipDismissTargetHandler mPipDismissTargetHandler;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private IPinnedStackController mPinnedStackController;
private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener;
- private final PipMenuActivityController mMenuController;
+ private final PhonePipMenuController mMenuController;
private final AccessibilityManager mAccessibilityManager;
private boolean mShowPipMenuOnAnimationEnd = false;
@@ -89,7 +86,7 @@ public class PipTouchHandler {
private boolean mEnableStash = true;
// The reference inset bounds, used to determine the dismiss fraction
- private Rect mInsetBounds = new Rect();
+ private final Rect mInsetBounds = new Rect();
private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
@@ -97,7 +94,8 @@ public class PipTouchHandler {
private int mDeferResizeToNormalBoundsUntilRotation = -1;
private int mDisplayRotation;
- private Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler();
+ private final PipAccessibilityInteractionConnection mConnection;
// Behaviour states
private int mMenuState = MENU_STATE_NONE;
@@ -111,7 +109,6 @@ public class PipTouchHandler {
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinDismiss;
- private PipAccessibilityInteractionConnection mConnection;
// Touch state
private final PipTouchState mTouchState;
@@ -125,7 +122,7 @@ public class PipTouchHandler {
/**
* A listener for the PIP menu activity.
*/
- private class PipMenuListener implements PipMenuActivityController.Listener {
+ private class PipMenuListener implements PhonePipMenuController.Listener {
@Override
public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) {
setMenuState(menuState, resize, callback);
@@ -152,12 +149,13 @@ public class PipTouchHandler {
@SuppressLint("InflateParams")
public PipTouchHandler(Context context,
- PipMenuActivityController menuController,
+ PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
FloatingContentCoordinator floatingContentCoordinator,
- PipUiEventLogger pipUiEventLogger) {
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor shellMainExecutor) {
// Initialize the Pip input consumer
mContext = context;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -188,7 +186,7 @@ public class PipTouchHandler {
mFloatingContentCoordinator = floatingContentCoordinator;
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
- this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
+ this::onAccessibilityShowMenu, this::updateMovementBounds, shellMainExecutor);
mPipUiEventLogger = pipUiEventLogger;
@@ -436,8 +434,11 @@ public class PipTouchHandler {
* TODO Add appropriate description
*/
public void onRegistrationChanged(boolean isRegistered) {
- mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
- ? mConnection : null);
+ if (isRegistered) {
+ mConnection.register(mAccessibilityManager);
+ } else {
+ mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+ }
if (!isRegistered && mTouchState.isUserInteracting()) {
// If the input consumer is unregistered while the user is interacting, then we may not
// get the final TOUCH_UP event, so clean up the dismiss target as well
@@ -459,10 +460,6 @@ public class PipTouchHandler {
if (!(inputEvent instanceof MotionEvent)) {
return true;
}
- // Skip touch handling until we are bound to the controller
- if (mPinnedStackController == null) {
- return true;
- }
MotionEvent ev = (MotionEvent) inputEvent;
if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
@@ -473,6 +470,11 @@ public class PipTouchHandler {
return true;
}
+ if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ return true;
+ }
+
if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
&& mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
@@ -586,13 +588,6 @@ public class PipTouchHandler {
}
/**
- * Sets the controller to update the system of changes from user interaction.
- */
- void setPinnedStackController(IPinnedStackController controller) {
- mPinnedStackController = controller;
- }
-
- /**
* Sets the menu visibility.
*/
private void setMenuState(int menuState, boolean resize, Runnable callback) {
@@ -620,13 +615,9 @@ public class PipTouchHandler {
// bounds which are now stale. In such a case we defer the animation to the
// normal bounds until after the next onMovementBoundsChanged() call to get the
// bounds in the new orientation
- try {
- int displayRotation = mPinnedStackController.getDisplayRotation();
- if (mDisplayRotation != displayRotation) {
- mDeferResizeToNormalBoundsUntilRotation = displayRotation;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Could not get display rotation from controller");
+ int displayRotation = mContext.getDisplay().getRotation();
+ if (mDisplayRotation != displayRotation) {
+ mDeferResizeToNormalBoundsUntilRotation = displayRotation;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 217150770084..5f2327ce98d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
import android.graphics.PointF;
import android.os.Handler;
import android.util.Log;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -64,6 +65,7 @@ public class PipTouchState {
private boolean mStartedDragging = false;
private boolean mAllowDraggingOffscreen = false;
private int mActivePointerId;
+ private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
public PipTouchState(ViewConfiguration viewConfig, Handler handler,
Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
@@ -81,12 +83,14 @@ public class PipTouchState {
mIsDragging = false;
mStartedDragging = false;
mIsUserInteracting = false;
+ mLastTouchDisplayId = Display.INVALID_DISPLAY;
}
/**
* Processes a given touch event and updates the state.
*/
public void onTouchEvent(MotionEvent ev) {
+ mLastTouchDisplayId = ev.getDisplayId();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (!mAllowTouches) {
@@ -266,6 +270,13 @@ public class PipTouchState {
}
/**
+ * @return Display ID of the last touch event.
+ */
+ public int getLastTouchDisplayId() {
+ return mLastTouchDisplayId;
+ }
+
+ /**
* Sets whether touching is currently allowed.
*/
public void setAllowTouches(boolean allowTouches) {
@@ -378,6 +389,7 @@ public class PipTouchState {
pw.println(prefix + TAG);
pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
+ pw.println(innerPrefix + "mLastTouchDisplayId=" + mLastTouchDisplayId);
pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
index 56e97b91c9d2..0955056900f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
@@ -36,9 +36,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -57,15 +55,14 @@ import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Objects;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
- private static final String TAG = "PipController";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String TAG = "TvPipController";
+ static final boolean DEBUG = false;
/**
* Unknown or invalid state
@@ -87,44 +84,21 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private static final int TASK_ID_NO_PIP = -1;
private static final int INVALID_RESOURCE_TYPE = -1;
- public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
-
- /**
- * PIPed activity is playing a media and it can be paused.
- */
- static final int PLAYBACK_STATE_PLAYING = 0;
- /**
- * PIPed activity has a paused media and it can be played.
- */
- static final int PLAYBACK_STATE_PAUSED = 1;
- /**
- * Users are unable to control PIPed activity's media playback.
- */
- static final int PLAYBACK_STATE_UNAVAILABLE = 2;
-
- private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
-
- private int mSuspendPipResizingReason;
-
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
+ private final TvPipMenuController mTvPipMenuController;
+ private final PipNotification mPipNotification;
private IActivityTaskManager mActivityTaskManager;
private int mState = STATE_NO_PIP;
- private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
private final Handler mHandler = new Handler();
- private List<Listener> mListeners = new ArrayList<>();
- private Rect mPipBounds;
- private Rect mDefaultPipBounds = new Rect();
- private Rect mMenuModePipBounds;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private int mPipTaskId = TASK_ID_NO_PIP;
private int mPinnedStackId = INVALID_STACK_ID;
private String[] mLastPackagesResourceGranted;
- private PipNotification mPipNotification;
private ParceledListSlice<RemoteAction> mCustomActions;
private WindowManagerShellWrapper mWindowManagerShellWrapper;
private int mResizeAnimationDuration;
@@ -137,9 +111,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private boolean mImeVisible;
private int mImeHeightAdjustment;
- private final Runnable mResizePinnedStackRunnable =
- () -> resizePinnedStack(mResumeResizePinnedStackRunnableState);
- private final Runnable mClosePipRunnable = () -> closePip();
+ private final Runnable mClosePipRunnable = this::closePip;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -181,46 +153,33 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- mHandler.post(() -> {
- mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
- if (mState == STATE_PIP) {
- if (mImeVisible != imeVisible) {
- if (imeVisible) {
- // Save the IME height adjustment, and offset to not occlude the IME
- mPipBounds.offset(0, -imeHeight);
- mImeHeightAdjustment = imeHeight;
- } else {
- // Apply the inverse adjustment when the IME is hidden
- mPipBounds.offset(0, mImeHeightAdjustment);
- }
- mImeVisible = imeVisible;
- resizePinnedStack(STATE_PIP);
+ mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
+ if (mState == STATE_PIP) {
+ if (mImeVisible != imeVisible) {
+ if (imeVisible) {
+ // Save the IME height adjustment, and offset to not occlude the IME
+ mPipBoundsState.getNormalBounds().offset(0, -imeHeight);
+ mImeHeightAdjustment = imeHeight;
+ } else {
+ // Apply the inverse adjustment when the IME is hidden
+ mPipBoundsState.getNormalBounds().offset(0, mImeHeightAdjustment);
}
+ mImeVisible = imeVisible;
+ resizePinnedStack(STATE_PIP);
}
- });
+ }
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
- mHandler.post(() -> {
- mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
-
- mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
- mPipBounds.set(mPipBoundsAlgorithm.getNormalBounds());
- if (mDefaultPipBounds.isEmpty()) {
- mDefaultPipBounds.set(mPipBoundsAlgorithm.getDefaultBounds());
- }
- });
+ mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
+ mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
mCustomActions = actions;
- mHandler.post(() -> {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
- }
- });
+ mTvPipMenuController.setAppActions(mCustomActions);
}
}
@@ -228,6 +187,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
+ TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
PipNotification pipNotification,
TaskStackListenerImpl taskStackListener,
@@ -237,6 +197,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipNotification = pipNotification;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipMediaController = pipMediaController;
+ mTvPipMenuController = tvPipMenuController;
+ mTvPipMenuController.attachPipController(this);
// Ensure that we have the display info in case we get calls to update the bounds
// before the listener calls back
final DisplayInfo displayInfo = new DisplayInfo();
@@ -249,8 +211,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipTaskOrganizer.registerPipTransitionCallback(this);
mActivityTaskManager = ActivityTaskManager.getService();
- addListener(mPipNotification);
-
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_MENU);
@@ -289,9 +249,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
PipController.this.onActivityRestartAttempt(task, clearedTask);
}
});
-
- // TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
- PipMenuActivity.setPipController(this);
}
private void loadConfigurationsAndApply(Configuration newConfig) {
@@ -302,20 +259,17 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
return;
}
- Resources res = mContext.getResources();
- mMenuModePipBounds = Rect.unflattenFromString(res.getString(
- R.string.pip_menu_bounds));
+ final Rect menuBounds = Rect.unflattenFromString(
+ mContext.getResources().getString(R.string.pip_menu_bounds));
+ mPipBoundsState.setExpandedBounds(menuBounds);
- // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
- // 1. Configuration changed due to the language change (RTL <-> RTL)
- // 2. SystemUI restarts after the crash
- mPipBounds = mDefaultPipBounds;
resizePinnedStack(getPinnedTaskInfo() == null ? STATE_NO_PIP : STATE_PIP);
}
/**
* Updates the PIP per configuration changed.
*/
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
loadConfigurationsAndApply(newConfig);
mPipNotification.onConfigurationChanged(mContext);
@@ -359,9 +313,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPinnedStackId = INVALID_STACK_ID;
}
}
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipActivityClosed();
- }
+ mPipNotification.dismiss();
+ mTvPipMenuController.hideMenu();
mHandler.removeCallbacks(mClosePipRunnable);
}
@@ -372,29 +325,33 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
mPipTaskId = TASK_ID_NO_PIP;
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onMoveToFullscreen();
- }
+ mTvPipMenuController.hideMenu();
+ mPipNotification.dismiss();
+
resizePinnedStack(STATE_NO_PIP);
}
private void onActivityPinned(String packageName) {
- if (DEBUG) Log.d(TAG, "onActivityPinned()");
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
+ final RootTaskInfo taskInfo = getPinnedTaskInfo();
+ if (DEBUG) Log.d(TAG, "onActivityPinned, task=" + taskInfo);
if (taskInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
return;
}
- if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo);
+
+ // At this point PipBoundsState knows the correct aspect ratio for this pinned task, so we
+ // use PipBoundsAlgorithm to calculate the normal bounds for the task (PipBoundsAlgorithm
+ // will query PipBoundsState for the aspect ratio) and pass the bounds over to the
+ // PipBoundsState.
+ mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
+
mPinnedStackId = taskInfo.taskId;
mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
+
// Set state to STATE_PIP so we show it when the pinned stack animation ends.
mState = STATE_PIP;
mPipMediaController.onActivityPinned();
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onPipEntered(packageName);
- }
+ mPipNotification.show(packageName);
}
private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -434,45 +391,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
if (getState() == STATE_PIP) {
- if (mPipBounds != mDefaultPipBounds) {
- mPipBounds = mDefaultPipBounds;
+ if (!Objects.equals(mPipBoundsState.getBounds(), mPipBoundsState.getNormalBounds())) {
resizePinnedStack(STATE_PIP);
}
}
}
/**
- * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
- *
- * @param reason The reason for suspending resizing operations on the Pip.
- */
- public void suspendPipResizing(int reason) {
- if (DEBUG) {
- Log.d(TAG,
- "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
- }
-
- mSuspendPipResizingReason |= reason;
- }
-
- /**
- * Resumes resizing operation on the Pip that was previously suspended.
- *
- * @param reason The reason resizing operations on the Pip was suspended.
- */
- public void resumePipResizing(int reason) {
- if ((mSuspendPipResizingReason & reason) == 0) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG,
- "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
- }
- mSuspendPipResizingReason &= ~reason;
- mHandler.post(mResizePinnedStackRunnable);
- }
-
- /**
* Resize the Pip to the appropriate size for the input state.
*
* @param state In Pip state also used to determine the new size for the Pip.
@@ -482,21 +407,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
+ getStateDescription(), new Exception());
}
-
- boolean wasStateNoPip = (mState == STATE_NO_PIP);
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onPipResizeAboutToStart();
- }
- if (mSuspendPipResizingReason != 0) {
- mResumeResizePinnedStackRunnableState = state;
- if (DEBUG) {
- Log.d(TAG, "resizePinnedStack() deferring"
- + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
- + " mResumeResizePinnedStackRunnableState="
- + stateToName(mResumeResizePinnedStackRunnableState));
- }
- return;
- }
+ final boolean wasStateNoPip = (mState == STATE_NO_PIP);
+ mTvPipMenuController.hideMenu();
mState = state;
final Rect newBounds;
switch (mState) {
@@ -509,11 +421,11 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
break;
case STATE_PIP_MENU:
- newBounds = mMenuModePipBounds;
+ newBounds = mPipBoundsState.getExpandedBounds();
break;
case STATE_PIP: // fallthrough
default:
- newBounds = mPipBounds;
+ newBounds = mPipBoundsState.getNormalBounds();
break;
}
if (newBounds != null) {
@@ -524,44 +436,17 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
/**
- * @return the current state, or the pending state if the state change was previously suspended.
+ * @return the current state.
*/
private int getState() {
- if (mSuspendPipResizingReason != 0) {
- return mResumeResizePinnedStackRunnableState;
- }
return mState;
}
- /**
- * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
- * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
- */
private void showPipMenu() {
if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
mState = STATE_PIP_MENU;
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onShowPipMenu();
- }
- Intent intent = new Intent(mContext, PipMenuActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
- mContext.startActivity(intent);
- }
-
- /**
- * Adds a {@link Listener} to PipController.
- */
- void addListener(Listener listener) {
- mListeners.add(listener);
- }
-
- /**
- * Removes a {@link Listener} from PipController.
- */
- void removeListener(Listener listener) {
- mListeners.remove(listener);
+ mTvPipMenuController.showMenu();
}
/**
@@ -635,35 +520,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
- /**
- * A listener interface to receive notification on changes in PIP.
- */
- public interface Listener {
- /**
- * Invoked when an activity is pinned and PIP manager is set corresponding information.
- * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
- * because there's no guarantee for the PIP manager be return relavent information
- * correctly. (e.g. {@link Pip.isPipShown}).
- */
- void onPipEntered(String packageName);
- /** Invoked when a PIPed activity is closed. */
- void onPipActivityClosed();
- /** Invoked when the PIP menu gets shown. */
- void onShowPipMenu();
- /** Invoked when the PIP menu actions change. */
- void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions);
- /** Invoked when the PIPed activity is about to return back to the fullscreen. */
- void onMoveToFullscreen();
- /** Invoked when we are above to start resizing the Pip. */
- void onPipResizeAboutToStart();
- }
-
private String getStateDescription() {
- if (mSuspendPipResizingReason == 0) {
- return stateToName(mState);
- }
- return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState)
- + " is suspended)";
+ return stateToName(mState);
}
private static String stateToName(int state) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java
deleted file mode 100644
index d2270c278161..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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.tv;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.app.Activity;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.wm.shell.R;
-
-import java.util.Collections;
-
-/**
- * Activity to show the PIP menu to control PIP.
- * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
- */
-public class PipMenuActivity extends Activity implements PipController.Listener {
- private static final String TAG = "PipMenuActivity";
- private static final boolean DEBUG = PipController.DEBUG;
-
- static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
-
- private static PipController sPipController;
-
- private Animator mFadeInAnimation;
- private Animator mFadeOutAnimation;
- private boolean mRestorePipSizeWhenClose;
- private PipControlsViewController mPipControlsViewController;
-
- @Override
- protected void onCreate(Bundle bundle) {
- if (DEBUG) Log.d(TAG, "onCreate()");
-
- super.onCreate(bundle);
- if (sPipController == null) {
- finish();
- }
- setContentView(R.layout.tv_pip_menu);
- mPipControlsViewController = new PipControlsViewController(
- findViewById(R.id.pip_controls), sPipController);
- sPipController.addListener(this);
- mRestorePipSizeWhenClose = true;
- mFadeInAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_in_animation);
- mFadeInAnimation.setTarget(mPipControlsViewController.getView());
- mFadeOutAnimation = AnimatorInflater.loadAnimator(
- this, R.anim.tv_pip_menu_fade_out_animation);
- mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent);
- super.onNewIntent(intent);
-
- onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
- }
-
- private void restorePipAndFinish() {
- if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
-
- if (mRestorePipSizeWhenClose) {
- if (DEBUG) Log.d(TAG, " > restoring to the default position");
-
- // When PIP menu activity is closed, restore to the default position.
- sPipController.resizePinnedStack(PipController.STATE_PIP);
- }
- finish();
- }
-
- @Override
- public void onResume() {
- if (DEBUG) Log.d(TAG, "onResume()");
-
- super.onResume();
- mFadeInAnimation.start();
- }
-
- @Override
- public void onPause() {
- if (DEBUG) Log.d(TAG, "onPause()");
-
- super.onPause();
- mFadeOutAnimation.start();
- restorePipAndFinish();
- }
-
- @Override
- protected void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy()");
-
- super.onDestroy();
- sPipController.removeListener(this);
- sPipController.resumePipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
- }
-
- @Override
- public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed()");
-
- restorePipAndFinish();
- }
-
- @Override
- public void onPipEntered(String packageName) {
- if (DEBUG) Log.d(TAG, "onPipEntered(), packageName=" + packageName);
- }
-
- @Override
- public void onPipActivityClosed() {
- if (DEBUG) Log.d(TAG, "onPipActivityClosed()");
-
- finish();
- }
-
- @Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
- if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
-
- boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
- mPipControlsViewController.setCustomActions(
- hasCustomActions ? actions.getList() : Collections.emptyList());
- }
-
- @Override
- public void onShowPipMenu() {
- if (DEBUG) Log.d(TAG, "onShowPipMenu()");
- }
-
- @Override
- public void onMoveToFullscreen() {
- if (DEBUG) Log.d(TAG, "onMoveToFullscreen()");
-
- // Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
- // This conflicts with restoring PIP position, so disable it.
- mRestorePipSizeWhenClose = false;
- finish();
- }
-
- @Override
- public void onPipResizeAboutToStart() {
- if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
-
- finish();
- sPipController.suspendPipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
- }
-
- @Override
- public void finish() {
- if (DEBUG) Log.d(TAG, "finish()", new RuntimeException());
-
- super.finish();
- }
-
- /**
- * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
- *
- * @param pipController The singleton pipController instance for TV
- */
- public static void setPipController(PipController pipController) {
- if (sPipController != null) {
- return;
- }
- sPipController = pipController;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java
new file mode 100644
index 000000000000..83cb7ce8065b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuView.java
@@ -0,0 +1,126 @@
+/*
+ * 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.tv;
+
+import static android.view.KeyEvent.ACTION_UP;
+import static android.view.KeyEvent.KEYCODE_BACK;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.Collections;
+
+/**
+ * The Menu View that shows controls of the PiP. Always fullscreen.
+ */
+public class PipMenuView extends FrameLayout {
+ private static final String TAG = "PipMenuView";
+ private static final boolean DEBUG = PipController.DEBUG;
+
+ private final Animator mFadeInAnimation;
+ private final Animator mFadeOutAnimation;
+ private final PipControlsViewController mPipControlsViewController;
+ @Nullable
+ private OnBackPressListener mOnBackPressListener;
+
+ public PipMenuView(Context context, PipController pipController) {
+ super(context, null, 0);
+ inflate(context, R.layout.tv_pip_menu, this);
+
+ mPipControlsViewController = new PipControlsViewController(
+ findViewById(R.id.pip_controls), pipController);
+ mFadeInAnimation = AnimatorInflater.loadAnimator(
+ mContext, R.anim.tv_pip_menu_fade_in_animation);
+ mFadeInAnimation.setTarget(mPipControlsViewController.getView());
+ mFadeOutAnimation = AnimatorInflater.loadAnimator(
+ mContext, R.anim.tv_pip_menu_fade_out_animation);
+ mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
+ }
+
+ @Nullable
+ SurfaceControl getWindowSurfaceControl() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ }
+ final SurfaceControl out = root.getSurfaceControl();
+ if (out != null && out.isValid()) {
+ return out;
+ }
+ return null;
+ }
+
+ void showMenu() {
+ mFadeInAnimation.start();
+ setAlpha(1.0f);
+ grantWindowFocus(true);
+ }
+
+ void hideMenu() {
+ mFadeOutAnimation.start();
+ setAlpha(0.0f);
+ grantWindowFocus(false);
+ }
+
+ private void grantWindowFocus(boolean grantFocus) {
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ getViewRootImpl().getInputToken(), grantFocus);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to update focus as menu disappears", e);
+ }
+ }
+
+ void setOnBackPressListener(OnBackPressListener onBackPressListener) {
+ mOnBackPressListener = onBackPressListener;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KEYCODE_BACK && event.getAction() == ACTION_UP
+ && mOnBackPressListener != null) {
+ mOnBackPressListener.onBackPress();
+ return true;
+ } else {
+ return super.dispatchKeyEvent(event);
+ }
+ }
+
+ void setAppActions(ParceledListSlice<RemoteAction> actions) {
+ if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
+
+ boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
+ mPipControlsViewController.setCustomActions(
+ hasCustomActions ? actions.getList() : Collections.emptyList());
+ }
+
+ interface OnBackPressListener {
+ void onBackPress();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
index b30dee4f331f..4e0ab668be81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
@@ -19,12 +19,10 @@ package com.android.wm.shell.pip.tv;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteAction;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
@@ -41,7 +39,7 @@ import java.util.Objects;
* <p>Once it's created, it will manage the PIP notification UI by itself except for handling
* configuration changes.
*/
-public class PipNotification implements PipController.Listener {
+public class PipNotification {
private static final boolean DEBUG = PipController.DEBUG;
private static final String TAG = "PipNotification";
@@ -81,43 +79,21 @@ public class PipNotification implements PipController.Listener {
onConfigurationChanged(context);
}
- @Override
- public void onPipEntered(String packageName) {
+ void show(String packageName) {
mPackageName = packageName;
- notifyPipNotification();
+ update();
}
- @Override
- public void onPipActivityClosed() {
- dismissPipNotification();
- mPackageName = null;
- }
-
- @Override
- public void onShowPipMenu() {
- // no-op.
- }
-
- @Override
- public void onPipMenuActionsChanged(ParceledListSlice<RemoteAction> actions) {
- // no-op.
- }
-
- @Override
- public void onMoveToFullscreen() {
- dismissPipNotification();
+ void dismiss() {
+ mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
+ mNotified = false;
mPackageName = null;
}
- @Override
- public void onPipResizeAboutToStart() {
- // no-op.
- }
-
private void onMediaMetadataChanged(MediaMetadata metadata) {
if (updateMediaControllerMetadata(metadata) && mNotified) {
// update notification
- notifyPipNotification();
+ update();
}
}
@@ -130,11 +106,11 @@ public class PipNotification implements PipController.Listener {
mDefaultIconResId = R.drawable.pip_icon;
if (mNotified) {
// update notification
- notifyPipNotification();
+ update();
}
}
- private void notifyPipNotification() {
+ private void update() {
mNotified = true;
mNotificationBuilder
.setShowWhen(true)
@@ -151,11 +127,6 @@ public class PipNotification implements PipController.Listener {
mNotificationBuilder.build());
}
- private void dismissPipNotification() {
- mNotified = false;
- mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
- }
-
private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
String title = null;
Bitmap art = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
new file mode 100644
index 000000000000..5d0d761abd93
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -0,0 +1,138 @@
+/*
+ * 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.tv;
+
+import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+
+/**
+ * Manages the visibility of the PiP Menu as user interacts with PiP.
+ */
+public class TvPipMenuController implements PipMenuController {
+ private static final String TAG = "TvPipMenuController";
+ private static final boolean DEBUG = PipController.DEBUG;
+
+ private final Context mContext;
+ private final SystemWindows mSystemWindows;
+ private final PipBoundsState mPipBoundsState;
+ private PipMenuView mMenuView;
+ private PipController mPipController;
+ private SurfaceControl mLeash;
+
+ public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
+ SystemWindows systemWindows) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mSystemWindows = systemWindows;
+ }
+
+ void attachPipController(PipController pipController) {
+ mPipController = pipController;
+ }
+
+ @Override
+ public void showMenu() {
+ if (DEBUG) Log.d(TAG, "showMenu()");
+
+ if (mMenuView != null) {
+ mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
+ mPipBoundsState.getDisplayBounds().width(),
+ mPipBoundsState.getDisplayBounds().height()));
+ mMenuView.showMenu();
+
+ // By default, SystemWindows views are above everything else.
+ // Set the relative z-order so the menu is below PiP.
+ if (mMenuView.getWindowSurfaceControl() != null && mLeash != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, -1);
+ t.apply();
+ }
+ }
+ }
+
+ void hideMenu() {
+ if (DEBUG) Log.d(TAG, "hideMenu()");
+
+ if (isMenuVisible()) {
+ mMenuView.hideMenu();
+ mPipController.resizePinnedStack(PipController.STATE_PIP);
+ }
+ }
+
+ @Override
+ public void attach(SurfaceControl leash) {
+ mLeash = leash;
+ attachPipMenuView();
+ }
+
+ @Override
+ public void detach() {
+ hideMenu();
+ detachPipMenuView();
+ mLeash = null;
+ }
+
+ private void attachPipMenuView() {
+ if (DEBUG) Log.d(TAG, "attachPipMenuView()");
+
+ if (mMenuView != null) {
+ detachPipMenuView();
+ }
+
+ mMenuView = new PipMenuView(mContext, mPipController);
+ mMenuView.setOnBackPressListener(this::hideMenu);
+ mSystemWindows.addView(mMenuView,
+ getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
+ }
+
+ private void detachPipMenuView() {
+ if (DEBUG) Log.d(TAG, "detachPipMenuView()");
+
+ if (mMenuView == null) {
+ return;
+ }
+
+ mSystemWindows.removeView(mMenuView);
+ mMenuView = null;
+ }
+
+ @Override
+ public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
+ if (DEBUG) Log.d(TAG, "setAppActions(), actions=" + appActions);
+
+ if (mMenuView != null) {
+ mMenuView.setAppActions(appActions);
+ } else {
+ Log.w(TAG, "Cannot set remote actions, there is no View");
+ }
+ }
+
+ @Override
+ public boolean isMenuVisible() {
+ return mMenuView != null && mMenuView.getAlpha() == 1.0f;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
index f2becc99eae8..5078371dab64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerWindowManager.java
@@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
@@ -59,7 +60,7 @@ final class DividerWindowManager {
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.setTitle(WINDOW_TITLE);
- mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
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 e55f065c1bb2..7c70a4efad91 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
@@ -19,6 +19,8 @@ package com.android.wm.shell.splitscreen;
import android.graphics.Rect;
import android.window.WindowContainerToken;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
import java.io.PrintWriter;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -26,6 +28,7 @@ import java.util.function.Consumer;
/**
* Interface to engage split screen feature.
*/
+@ExternalThread
public interface SplitScreen {
/** Called when keyguard showing state changed. */
void onKeyguardVisibilityChanged(boolean isShowing);
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 07af289c4f35..6d6c76139c87 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
@@ -506,7 +506,7 @@ public class SplitScreenController implements SplitScreen,
// Try fetching the top running task.
final List<RunningTaskInfo> runningTasks =
- ActivityTaskManager.getService().getTasks(1 /* maxNum */);
+ ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
if (runningTasks == null || runningTasks.isEmpty()) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
index 5b2b38ba8189..af451ae8bc50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
@@ -205,12 +205,9 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
private void updateChildTaskSurface(
RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) {
- final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds();
final Point taskPositionInParent = taskInfo.positionInParent;
- final Rect corp = new Rect(taskBounds);
- corp.offset(-taskBounds.left, -taskBounds.top);
mSyncQueue.runInSync(t -> {
- t.setWindowCrop(leash, corp);
+ t.setWindowCrop(leash, null);
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) {
t.setAlpha(leash, 1f);
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index d7afa0e166b3..4a498d2ec581 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -29,7 +29,11 @@ android_test {
"truth-prebuilt",
"app-helpers-core",
"launcher-helper-lib",
- "launcher-aosp-tapl"
+ "launcher-aosp-tapl",
+ "wm-flicker-common-assertions",
+ "wm-flicker-common-app-helpers",
+ "platform-test-annotations",
+ "wmshell-flicker-test-components",
],
}
@@ -47,6 +51,10 @@ android_test {
"truth-prebuilt",
"app-helpers-core",
"launcher-helper-lib",
- "launcher-aosp-tapl"
+ "launcher-aosp-tapl",
+ "wm-flicker-common-assertions",
+ "wm-flicker-common-app-helpers",
+ "platform-test-annotations",
+ "wmshell-flicker-test-components",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 9e8330973b40..1054c4345891 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -21,6 +21,8 @@
<!-- Read and write traces from external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Write secure settings -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<!-- Capture screen contents -->
@@ -36,7 +38,10 @@
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<!-- Control test app's media session -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
- <application>
+ <!-- ATM.removeRootTasksWithActivityTypes() -->
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Allow the test to write directly to /sdcard/ -->
+ <application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
index 9dd9f42bdf81..23d7021baffb 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
@@ -34,7 +34,7 @@
<option name="hidden-api-checks" value="false" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/storage/emulated/0/Android/data/com.android.wm.shell.flicker/files" />
+ <option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
index afb1166415fc..073860875004 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
@@ -34,7 +34,7 @@
<option name="hidden-api-checks" value="false" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/storage/emulated/0/Android/data/com.android.wm.shell.flicker/files" />
+ <option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
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 0fb43e263d05..1638d72f9914 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
@@ -18,78 +18,6 @@ package com.android.wm.shell.flicker
import com.android.server.wm.flicker.dsl.EventLogAssertion
import com.android.server.wm.flicker.dsl.LayersAssertion
-import com.android.server.wm.flicker.dsl.WmAssertion
-import com.android.server.wm.flicker.helpers.WindowUtils
-
-@JvmOverloads
-fun WmAssertion.statusBarWindowIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun WmAssertion.navBarWindowIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("navBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.noUncoveredRegions(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- allStates: Boolean = true,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
- val endingBounds = WindowUtils.getDisplayBounds(endRotation)
- if (allStates) {
- all("noUncoveredRegions", bugId, enabled) {
- if (startingBounds == endingBounds) {
- this.coversAtLeastRegion(startingBounds)
- } else {
- this.coversAtLeastRegion(startingBounds)
- .then()
- .coversAtLeastRegion(endingBounds)
- }
- }
- } else {
- start("noUncoveredRegions_StartingPos") {
- this.coversAtLeastRegion(startingBounds)
- }
- end("noUncoveredRegions_EndingPos") {
- this.coversAtLeastRegion(endingBounds)
- }
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.statusBarLayerIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.navBarLayerIsAlwaysVisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
- }
-}
@JvmOverloads
fun LayersAssertion.appPairsDividerIsVisible(
@@ -97,7 +25,7 @@ fun LayersAssertion.appPairsDividerIsVisible(
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+ this.showsLayer(FlickerTestBase.SPLIT_DIVIDER)
}
}
@@ -107,7 +35,7 @@ fun LayersAssertion.appPairsDividerIsInvisible(
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsInVisible", bugId, enabled) {
- this.hasNotLayer(FlickerTestBase.APP_PAIRS_DIVIDER)
+ this.hasNotLayer(FlickerTestBase.SPLIT_DIVIDER)
}
}
@@ -131,48 +59,6 @@ fun LayersAssertion.dockedStackDividerIsInvisible(
}
}
-@JvmOverloads
-fun LayersAssertion.navBarLayerRotatesAndScales(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
- val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
-
- start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- }
- end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, endingPos)
- }
-
- if (startingPos == endingPos) {
- all("navBarLayerRotatesAndScales", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- }
- }
-}
-
-@JvmOverloads
-fun LayersAssertion.statusBarLayerRotatesScales(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
-) {
- val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
- val endingPos = WindowUtils.getStatusBarPosition(endRotation)
-
- start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, startingPos)
- }
- end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, endingPos)
- }
-}
-
fun EventLogAssertion.focusChanges(
vararg windows: String,
bugId: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 3c222e7d8b56..96234fcc8570 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -16,31 +16,24 @@
package com.android.wm.shell.flicker
-import android.content.ComponentName
-
const val IME_WINDOW_NAME = "InputMethod"
-const val PIP_WINDOW_NAME = "PipMenuActivity"
-const val SPLITSCREEN_PRIMARY_WINDOW_NAME = "SplitScreenActivity"
-const val SPLITSCREEN_SECONDARY_WINDOW_NAME = "SplitScreenSecondaryActivity"
+const val PIP_MENU_WINDOW_NAME = "PipMenuActivity"
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
const val TEST_APP_PACKAGE_NAME = "com.android.wm.shell.flicker.testapp"
// Test App > Pip Activity
-val TEST_APP_PIP_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
- TEST_APP_PACKAGE_NAME, ".PipActivity")
const val TEST_APP_PIP_ACTIVITY_LABEL = "PipApp"
-const val TEST_APP_PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
+const val TEST_APP_PIP_MENU_ACTION_NO_OP = "No-Op"
+const val TEST_APP_PIP_MENU_ACTION_ON = "On"
+const val TEST_APP_PIP_MENU_ACTION_OFF = "Off"
+const val TEST_APP_PIP_MENU_ACTION_CLEAR = "Clear"
// Test App > Ime Activity
-val TEST_APP_IME_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
- TEST_APP_PACKAGE_NAME, ".ImeActivity")
const val TEST_APP_IME_ACTIVITY_LABEL = "ImeApp"
+// Test App > Test Activity
+const val TEST_APP_FIXED_ACTIVITY_LABEL = "FixedApp"
// Test App > SplitScreen Activity
-val TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
- TEST_APP_PACKAGE_NAME, ".$SPLITSCREEN_PRIMARY_WINDOW_NAME")
-val TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
- TEST_APP_PACKAGE_NAME, ".$SPLITSCREEN_SECONDARY_WINDOW_NAME")
const val TEST_APP_SPLITSCREEN_PRIMARY_LABEL = "SplitScreenPrimaryApp"
const val TEST_APP_SPLITSCREEN_SECONDARY_LABEL = "SplitScreenSecondaryApp"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 54b8fdc83a1f..7809be04de96 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -130,7 +130,7 @@ abstract class FlickerTestBase {
const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
- const val APP_PAIRS_DIVIDER = "AppPairDivider"
+ const val SPLIT_DIVIDER = "SplitDivider"
const val IMAGE_WALLPAPER = "ImageWallpaper"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
index 2fc6944a3a5f..0f8d30a94ec6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Presubmit
import android.os.SystemClock
import android.util.Log
import android.view.Surface
@@ -28,10 +29,10 @@ import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.TEST_REPETITIONS
import com.android.wm.shell.flicker.appPairsDividerIsInvisible
import com.android.wm.shell.flicker.appPairsDividerIsVisible
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+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 org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,6 +44,7 @@ import java.io.IOException
* Test AppPairs launch.
* To run this test: `atest WMShellFlickerTests:AppPairsTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -106,7 +108,7 @@ class AppPairsTest(
end("appsEndingBounds", enabled = false) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(SPLIT_DIVIDER)
this.hasVisibleRegion(primaryApp.defaultWindowName,
appPairsHelper.getPrimaryBounds(dividerRegion))
.and()
@@ -150,7 +152,7 @@ class AppPairsTest(
start("appsStartingBounds", enabled = false) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(APP_PAIRS_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(SPLIT_DIVIDER)
this.hasVisibleRegion(primaryApp.defaultWindowName,
appPairsHelper.getPrimaryBounds(dividerRegion))
.and()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
index 1a4de0a80bec..f32cd8842074 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestBase.kt
@@ -17,12 +17,11 @@
package com.android.wm.shell.flicker.apppairs
import com.android.wm.shell.flicker.NonRotationTestBase
-import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
-import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.testapp.Components
abstract class AppPairsTestBase(
rotationName: String,
@@ -30,11 +29,11 @@ abstract class AppPairsTestBase(
) : NonRotationTestBase(rotationName, rotation) {
protected val appPairsHelper = AppPairsHelper(instrumentation,
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
- TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+ Components.SplitScreenActivity())
protected val primaryApp = SplitScreenHelper(instrumentation,
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
- TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+ Components.SplitScreenActivity())
protected val secondaryApp = SplitScreenHelper(instrumentation,
TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
- TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
+ Components.SplitScreenSecondaryActivity())
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index 3b6fcdbee4be..e2cda7ad123d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,19 +17,19 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.graphics.Region
import android.system.helpers.ActivityHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.wm.shell.flicker.testapp.Components
class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentName: ComponentName
+ componentsInfo: Components.ComponentsInfo
) : BaseAppHelper(
instrumentation,
activityLabel,
- componentName
+ componentsInfo
) {
val activityHelper = ActivityHelper.getInstance()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 22496a506e0d..6fd1df3b3f30 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.FEATURE_LEANBACK
@@ -28,15 +27,15 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.wm.shell.flicker.TEST_APP_PACKAGE_NAME
+import com.android.wm.shell.flicker.testapp.Components
abstract class BaseAppHelper(
instrumentation: Instrumentation,
launcherName: String,
- private val launcherActivityComponent: ComponentName
+ private val componentsInfo: Components.ComponentsInfo
) : StandardAppHelper(
instrumentation,
- TEST_APP_PACKAGE_NAME,
+ Components.PACKAGE_NAME,
launcherName,
LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
) {
@@ -53,7 +52,7 @@ abstract class BaseAppHelper(
}
val defaultWindowName: String
- get() = launcherActivityComponent.className
+ get() = componentsInfo.activityName
val label: String
get() = context.packageManager.run {
@@ -63,8 +62,12 @@ abstract class BaseAppHelper(
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
- fun launchViaIntent() {
- context.startActivity(openAppIntent)
+ fun launchViaIntent(stringExtras: Map<String, String> = mapOf()) {
+ val intent = openAppIntent
+ stringExtras.forEach() {
+ intent.putExtra(it.key, it.value)
+ }
+ context.startActivity(intent)
uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS)
}
@@ -75,7 +78,7 @@ abstract class BaseAppHelper(
override fun getOpenAppIntent(): Intent {
val intent = Intent()
- intent.component = launcherActivityComponent
+ intent.component = componentsInfo.componentName
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return intent
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
new file mode 100644
index 000000000000..c7f19a5d2620
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.helpers
+
+import android.app.Instrumentation
+import com.android.wm.shell.flicker.TEST_APP_FIXED_ACTIVITY_LABEL
+import com.android.wm.shell.flicker.testapp.Components
+
+class FixedAppHelper(
+ instrumentation: Instrumentation
+) : BaseAppHelper(
+ instrumentation,
+ TEST_APP_FIXED_ACTIVITY_LABEL,
+ Components.FixedActivity()
+) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index a6650d7f13d1..d580104ade19 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -21,8 +21,8 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.waitForIME
-import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_LABEL
+import com.android.wm.shell.flicker.testapp.Components
import org.junit.Assert
open class ImeAppHelper(
@@ -30,7 +30,7 @@ open class ImeAppHelper(
) : BaseAppHelper(
instrumentation,
TEST_APP_IME_ACTIVITY_LABEL,
- TEST_APP_IME_ACTIVITY_COMPONENT_NAME
+ Components.ImeActivity()
) {
fun openIME() {
val editText = uiDevice.wait(
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 532b3de6c99e..ed5f8a42258b 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,8 @@ import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_LABEL
+import com.android.wm.shell.flicker.testapp.Components
import org.junit.Assert.assertNotNull
import org.junit.Assert.fail
@@ -36,7 +36,7 @@ class PipAppHelper(
) : BaseAppHelper(
instrumentation,
TEST_APP_PIP_ACTIVITY_LABEL,
- TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
+ Components.PipActivity()
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
@@ -70,6 +70,12 @@ class PipAppHelper(
startButton.click()
}
+ fun checkWithCustomActionsCheckbox() = uiDevice
+ .findObject(By.res(packageName, "with_custom_actions"))
+ ?.takeIf { it.isCheckable }
+ ?.apply { if (!isChecked) click() }
+ ?: error("'With custom actions' checkbox not found")
+
fun pauseMedia() = mediaController?.transportControls?.pause()
?: error("No active media session found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 10daa675ce36..e67fc97dad2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -17,19 +17,19 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.graphics.Region
import android.os.SystemClock
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.wm.shell.flicker.testapp.Components
class SplitScreenHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentName: ComponentName
+ componentsInfo: Components.ComponentsInfo
) : BaseAppHelper(
instrumentation,
activityLabel,
- componentName
+ componentsInfo
) {
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
new file mode 100644
index 000000000000..2015f4941cea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.pip
+
+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
+import android.os.SystemClock
+import com.android.wm.shell.flicker.NonRotationTestBase
+
+abstract class AppTestBase(
+ rotationName: String,
+ rotation: Int
+) : NonRotationTestBase(rotationName, rotation) {
+ companion object {
+ 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)
+ }
+
+ fun waitForAnimationComplete() {
+ // TODO: UiDevice doesn't have reliable way to wait for the completion of animation.
+ // Consider to introduce WindowManagerStateHelper to access Activity state.
+ SystemClock.sleep(1000)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
new file mode 100644
index 000000000000..2a660747bc1d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.pip
+
+internal const val PIP_WINDOW_TITLE = "PipMenuActivity"
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
new file mode 100644
index 000000000000..c9396aa7ea63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+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.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch and exit.
+ * To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterExitPipTest(
+ rotationName: String,
+ rotation: Int
+) : AppTestBase(rotationName, rotation) {
+ private val pipApp = PipAppHelper(instrumentation)
+ private val testApp = FixedAppHelper(instrumentation)
+
+ @Test
+ fun testDisplayMetricsPinUnpin() {
+ runFlicker(instrumentation) {
+ withTestName { "testDisplayMetricsPinUnpin" }
+ setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
+ testApp.launchViaIntent()
+ waitForAnimationComplete()
+ }
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.launchViaIntent()
+ waitForAnimationComplete()
+ }
+ teardown {
+ test {
+ removeAllTasksButHome()
+ }
+ }
+ assertions {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ windowManagerTrace {
+ all("pipApp must remain inside visible bounds") {
+ coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ all("Initially shows both app windows then pipApp hides testApp") {
+ showsAppWindow(testApp.defaultWindowName)
+ .and().showsAppWindowOnTop(pipApp.defaultWindowName)
+ .then()
+ .hidesAppWindow(testApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ all("Initially shows both app layers then pipApp hides testApp") {
+ showsLayer(testApp.defaultWindowName)
+ .and().showsLayer(pipApp.defaultWindowName)
+ .then()
+ .hidesLayer(testApp.defaultWindowName)
+ }
+ start("testApp covers the fullscreen, pipApp remains inside display") {
+ hasVisibleRegion(testApp.defaultWindowName, displayBounds)
+ coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
+ }
+ end("pipApp covers the fullscreen") {
+ hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
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 a1da7c939f60..cb1fe4e2981d 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
@@ -19,104 +19,113 @@ package com.android.wm.shell.flicker.pip
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.expandPipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarLayerRotatesAndScales
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.noUncoveredRegions
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerRotatesScales
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.PIP_WINDOW_NAME
+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.repetitions
+import com.android.server.wm.flicker.startRotation
+import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipToAppTest`
+ * To run this test: `atest WMShellFlickerTests:EnterPipTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class EnterPipTest(
- rotationName: String,
- rotation: Int
-) : PipTestBase(rotationName, rotation) {
- @Test
- fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("enterPip", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- device.pressHome()
- testApp.open()
- this.setRotation(rotation)
- }
- }
- teardown {
- eachRun {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("enterPip", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ device.pressHome()
+ testApp.open()
+ this.setRotation(configuration.startRotation)
+ }
}
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ teardown {
+ eachRun {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ }
}
- }
- }
- transitions {
- testApp.clickEnterPipButton()
- device.expandPipWindow()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- all("pipWindowBecomesVisible") {
- this.showsAppWindow(testApp.`package`)
- .then()
- .showsAppWindow(PIP_WINDOW_NAME)
+ transitions {
+ testApp.clickEnterPipButton()
+ device.expandPipWindow()
}
- }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
- navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
- statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
+ all("pipWindowBecomesVisible") {
+ this.showsAppWindow(testApp.`package`)
+ .then()
+ .showsAppWindow(PIP_WINDOW_TITLE)
+ }
+ }
- all("pipLayerBecomesVisible") {
- this.showsLayer(testApp.launcherName)
- .then()
- .showsLayer(PIP_WINDOW_NAME)
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+ }
+
+ layersTrace {
+ all("pipLayerBecomesVisible") {
+ this.showsLayer(testApp.launcherName)
+ .then()
+ .showsLayer(PIP_WINDOW_TITLE)
+ }
+ }
}
}
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index abb8fc52abbb..6b44ce6ace0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,33 +16,28 @@
package com.android.wm.shell.flicker.pip
-import android.content.ComponentName
-import android.graphics.Region
-import android.util.Log
+import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.view.WindowManager
import androidx.test.filters.RequiresDevice
-import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
import com.android.wm.shell.flicker.IME_WINDOW_NAME
-import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_WINDOW_NAME
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-import java.io.IOException
/**
* Test Pip launch.
* To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -50,9 +45,6 @@ class PipKeyboardTest(
rotationName: String,
rotation: Int
) : PipTestBase(rotationName, rotation) {
- private val windowManager: WindowManager =
- instrumentation.context.getSystemService(WindowManager::class.java)
-
private val keyboardApp = ImeAppHelper(instrumentation)
private val keyboardScenario: FlickerBuilder
@@ -71,7 +63,7 @@ class PipKeyboardTest(
// open an app with an input field and a keyboard
// UiAutomator doesn't support to launch the multiple Activities in a task.
// So use launchActivity() for the Keyboard Activity.
- launchActivity(TEST_APP_IME_ACTIVITY_COMPONENT_NAME)
+ keyboardApp.launchViaIntent()
}
}
teardown {
@@ -103,10 +95,8 @@ class PipKeyboardTest(
assertions {
windowManagerTrace {
all("PiP window must remain inside visible bounds") {
- coversAtMostRegion(
- partialWindowTitle = "PipActivity",
- region = Region(windowManager.maximumWindowMetrics.bounds)
- )
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ coversAtMostRegion(testApp.defaultWindowName, displayBounds)
}
}
}
@@ -132,74 +122,13 @@ class PipKeyboardTest(
assertions {
windowManagerTrace {
end {
- isAboveWindow(IME_WINDOW_NAME, TEST_APP_PIP_ACTIVITY_WINDOW_NAME)
+ isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
}
}
}
}
}
- private fun launchActivity(
- activity: ComponentName? = null,
- action: String? = null,
- flags: Set<Int> = setOf(),
- boolExtras: Map<String, Boolean> = mapOf(),
- intExtras: Map<String, Int> = mapOf(),
- stringExtras: Map<String, String> = mapOf()
- ) {
- require(activity != null || !action.isNullOrBlank()) {
- "Cannot launch an activity with neither activity name nor action!"
- }
- val command = composeCommand(
- "start", activity, action, flags, boolExtras, intExtras, stringExtras)
- executeShellCommand(command)
- }
-
- private fun composeCommand(
- command: String,
- activity: ComponentName?,
- action: String?,
- flags: Set<Int>,
- boolExtras: Map<String, Boolean>,
- intExtras: Map<String, Int>,
- stringExtras: Map<String, String>
- ): String = buildString {
- append("am ")
- append(command)
- activity?.let {
- append(" -n ")
- append(it.flattenToShortString())
- }
- action?.let {
- append(" -a ")
- append(it)
- }
- flags.forEach {
- append(" -f ")
- append(it)
- }
- boolExtras.forEach {
- append(it.withFlag("ez"))
- }
- intExtras.forEach {
- append(it.withFlag("ei"))
- }
- stringExtras.forEach {
- append(it.withFlag("es"))
- }
- }
-
- private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
-
- private fun executeShellCommand(cmd: String): String {
- try {
- return SystemUtil.runShellCommand(instrumentation, cmd)
- } catch (e: IOException) {
- Log.e("FlickerTests", "Error running shell command: $cmd")
- throw e
- }
- }
-
companion object {
private const val TEST_REPETITIONS = 10
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
new file mode 100644
index 000000000000..76aabc1b83cf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.pip
+
+import android.content.Intent
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+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.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import org.junit.Assert.assertEquals
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipOrientationTest(
+ rotationName: String,
+ rotation: Int
+) : AppTestBase(rotationName, rotation) {
+ // Helper class to process test actions by broadcast.
+ private inner class BroadcastActionTrigger {
+ private fun createIntentWithAction(broadcastAction: String): Intent {
+ return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+ fun doAction(broadcastAction: String) {
+ instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction))
+ }
+ fun requestOrientationForPip(orientation: Int) {
+ instrumentation.getContext()
+ .sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
+ .putExtra(EXTRA_PIP_ORIENTATION, orientation.toString()))
+ }
+ }
+ private val broadcastActionTrigger = BroadcastActionTrigger()
+
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ private val ORIENTATION_LANDSCAPE = 0
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ private val ORIENTATION_PORTRAIT = 1
+
+ private val testApp = FixedAppHelper(instrumentation)
+ private val pipApp = PipAppHelper(instrumentation)
+
+ @Test
+ fun testEnterPipToOtherOrientation() {
+ runFlicker(instrumentation) {
+ withTestName { "testEnterPipToOtherOrientation" }
+ setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ // Launch a portrait only app on the fullscreen stack
+ testApp.launchViaIntent(stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
+ waitForAnimationComplete()
+ // Launch the PiP activity fixed as landscape
+ pipApp.launchViaIntent(stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+ waitForAnimationComplete()
+ }
+ }
+ transitions {
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back
+ // in portrait
+ broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+ waitForAnimationComplete()
+ }
+ teardown {
+ test {
+ removeAllTasksButHome()
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ all("pipApp window is always on top") {
+ showsAppWindowOnTop(pipApp.defaultWindowName)
+ }
+ start("pipApp window hides testApp") {
+ hidesAppWindow(testApp.defaultWindowName)
+ }
+ end("testApp windows is shown") {
+ showsAppWindow(testApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+ val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ start("pipApp layer hides testApp") {
+ hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
+ hidesLayer(testApp.defaultWindowName)
+ }
+ end("testApp layer covers fullscreen") {
+ hasVisibleRegion(testApp.defaultWindowName, endingBounds)
+ }
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testSetRequestedOrientationWhilePinned() {
+ runFlicker(instrumentation) {
+ withTestName { "testSetRequestedOrientationWhilePinned" }
+ setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ // Launch the PiP activity fixed as landscape
+ pipApp.launchViaIntent(stringExtras = mapOf(
+ EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
+ EXTRA_ENTER_PIP to "true"))
+ waitForAnimationComplete()
+ assertEquals(Surface.ROTATION_0, device.displayRotation)
+ }
+ }
+ transitions {
+ // Request that the orientation is set to landscape
+ broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
+
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
+ pipApp.launchViaIntent()
+ waitForAnimationComplete()
+ assertEquals(Surface.ROTATION_90, device.displayRotation)
+ }
+ teardown {
+ test {
+ removeAllTasksButHome()
+ }
+ }
+ assertions {
+ val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+ windowManagerTrace {
+ start("PIP window must remain inside display") {
+ coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
+ }
+ end("pipApp shows on top") {
+ showsAppWindowOnTop(pipApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ start("PIP layer must remain inside display") {
+ coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
+ }
+ end("pipApp layer covers fullscreen") {
+ hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
+ }
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
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
new file mode 100644
index 000000000000..a67b3b760c49
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
+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.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+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.statusBarLayerRotatesScales
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip Stack in bounds after rotations.
+ * To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipRotationTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = FixedAppHelper(instrumentation)
+ val pipApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation,
+ listOf(Surface.ROTATION_0, Surface.ROTATION_90))
+ .buildRotationTest { configuration ->
+ withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ AppTestBase.removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ pipApp.launchViaIntent(stringExtras = mapOf(
+ EXTRA_ENTER_PIP to "true"))
+ testApp.launchViaIntent()
+ AppTestBase.waitForAnimationComplete()
+ }
+ eachRun {
+ setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ setRotation(configuration.endRotation)
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ AppTestBase.removeAllTasksButHome()
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation,
+ configuration.endRotation, allStates = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ configuration.endRotation)
+ }
+ layersTrace {
+ val startingBounds = WindowUtils.getDisplayBounds(
+ configuration.startRotation)
+ val endingBounds = WindowUtils.getDisplayBounds(
+ configuration.endRotation)
+ start("appLayerRotates_StartingBounds") {
+ hasVisibleRegion(testApp.defaultWindowName, startingBounds)
+ coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
+ }
+ end("appLayerRotates_EndingBounds") {
+ hasVisibleRegion(testApp.defaultWindowName, endingBounds)
+ coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt
new file mode 100644
index 000000000000..f79b21ff278d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipSplitScreenTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.pip
+
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.ImeAppHelper
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.wm.shell.flicker.helpers.PipAppHelper
+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.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip with split-screen.
+ * To run this test: `atest WMShellFlickerTests:PipSplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 161435597)
+class PipSplitScreenTest(
+ rotationName: String,
+ rotation: Int
+) : AppTestBase(rotationName, rotation) {
+ private val pipApp = PipAppHelper(instrumentation)
+ private val imeApp = ImeAppHelper(instrumentation)
+ private val testApp = FixedAppHelper(instrumentation)
+
+ @Test
+ fun testShowsPipLaunchingToSplitScreen() {
+ runFlicker(instrumentation) {
+ withTestName { "testShowsPipLaunchingToSplitScreen" }
+ repeat { TEST_REPETITIONS }
+ setup {
+ test {
+ removeAllTasksButHome()
+ device.wakeUpAndGoToHomeScreen()
+ pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
+ waitForAnimationComplete()
+ }
+ }
+ transitions {
+ testApp.launchViaIntent()
+ device.launchSplitScreen()
+ imeApp.launchViaIntent()
+ waitForAnimationComplete()
+ }
+ teardown {
+ eachRun {
+ imeApp.exit()
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ testApp.exit()
+ }
+ test {
+ removeAllTasksButHome()
+ }
+ }
+ assertions {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ windowManagerTrace {
+ all("PIP window must remain inside visible bounds") {
+ coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
+ }
+ end("Both app windows should be visible") {
+ showsAppWindow(testApp.defaultWindowName)
+ showsAppWindow(imeApp.defaultWindowName)
+ noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName)
+ }
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ layersTrace {
+ all("PIP layer must remain inside visible bounds") {
+ coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
+ }
+ end("Both app layers should be visible") {
+ coversAtMostRegion(displayBounds, testApp.defaultWindowName)
+ coversAtMostRegion(displayBounds, imeApp.defaultWindowName)
+ }
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ }
+ }
+ }
+
+ companion object {
+ const val TEST_REPETITIONS = 2
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
index c1c34ecfbaec..03a92119fc86 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
@@ -16,12 +16,11 @@
package com.android.wm.shell.flicker.pip
-import com.android.wm.shell.flicker.NonRotationTestBase
import com.android.wm.shell.flicker.helpers.PipAppHelper
abstract class PipTestBase(
rotationName: String,
rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
+) : AppTestBase(rotationName, rotation) {
protected val testApp = PipAppHelper(instrumentation)
}
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
new file mode 100644
index 000000000000..e1fa6578e552
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.pip
+
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.closePipWindow
+import com.android.server.wm.flicker.helpers.expandPipWindow
+import com.android.server.wm.flicker.helpers.hasPipWindow
+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.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 org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipToAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 152738416)
+class PipToAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.pressHome()
+ testApp.open()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ testApp.clickEnterPipButton()
+ device.hasPipWindow()
+ }
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ }
+ }
+ transitions {
+ device.expandPipWindow()
+ device.waitForIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+
+ all("appReplacesPipWindow") {
+ this.showsAppWindow(PIP_WINDOW_TITLE)
+ .then()
+ .showsAppWindowOnTop(testApp.launcherName)
+ }
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+
+ all("appReplacesPipLayer") {
+ this.showsLayer(PIP_WINDOW_TITLE)
+ .then()
+ .showsLayer(testApp.launcherName)
+ }
+ }
+
+ eventLog {
+ focusChanges(
+ "NexusLauncherActivity", testApp.launcherName,
+ "NexusLauncherActivity", bugId = 151179149)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
new file mode 100644
index 000000000000..bf1193786a59
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.pip
+
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.closePipWindow
+import com.android.server.wm.flicker.helpers.hasPipWindow
+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.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 org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipToHomeTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 152738416)
+class PipToHomeTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.pressHome()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.startRotation)
+ testApp.clickEnterPipButton()
+ device.hasPipWindow()
+ }
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ }
+ }
+ transitions {
+ testApp.closePipWindow()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+
+ all("pipWindowBecomesInvisible") {
+ this.showsAppWindow(PIP_WINDOW_TITLE)
+ .then()
+ .hidesAppWindow(PIP_WINDOW_TITLE)
+ }
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+
+ all("pipLayerBecomesInvisible") {
+ this.showsLayer(PIP_WINDOW_TITLE)
+ .then()
+ .hidesLayer(PIP_WINDOW_TITLE)
+ }
+ }
+
+ eventLog {
+ focusChanges(testApp.launcherName, "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 871732cf7460..4cb6447f7d7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -20,7 +20,12 @@ import android.graphics.Rect
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.UiObject2
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.TEST_APP_PIP_MENU_ACTION_CLEAR
+import com.android.wm.shell.flicker.TEST_APP_PIP_MENU_ACTION_NO_OP
+import com.android.wm.shell.flicker.TEST_APP_PIP_MENU_ACTION_OFF
+import com.android.wm.shell.flicker.TEST_APP_PIP_MENU_ACTION_ON
import com.android.wm.shell.flicker.wait
+import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -128,6 +133,7 @@ class TvPipMenuTests : TvPipTestBase() {
testApp.clickStartMediaSessionButton()
enterPip_openMenu_assertShown()
+ assertFullscreenAndCloseButtonsAreShown()
// PiP menu should contain the Pause button
val pauseButton = uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
@@ -137,6 +143,7 @@ class TvPipMenuTests : TvPipTestBase() {
// When we pause media, the button should change from Pause to Play
pauseButton.click()
+ assertFullscreenAndCloseButtonsAreShown()
// PiP menu should contain the Play button now
uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription)
?: fail("\"Play\" button should be shown in Pip menu if there is an active " +
@@ -145,10 +152,99 @@ class TvPipMenuTests : TvPipTestBase() {
testApp.closePipWindow()
}
+ @Test
+ fun pipMenu_withCustomActions() {
+ // Enter PiP with custom actions.
+ testApp.checkWithCustomActionsCheckbox()
+ enterPip_openMenu_assertShown()
+
+ // PiP menu should contain "No-Op", "Off" and "Clear" buttons...
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_NO_OP)
+ ?: fail("\"No-Op\" button should be shown in Pip menu")
+ val offButton = uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_OFF)
+ ?: fail("\"Off\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
+ // ... and should also contain the "Full screen" and "Close" buttons.
+ assertFullscreenAndCloseButtonsAreShown()
+
+ offButton.click()
+ // Invoking the "Off" action should replace it with the "On" action/button and should
+ // remove the "No-Op" action/button. "Clear" action/button should remain in the menu ...
+ uiDevice.waitForTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_ON)
+ ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
+ assertNull("\"No-Op\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_NO_OP))
+ val clearButton =
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
+ // ... as well as the "Full screen" and "Close" buttons.
+ assertFullscreenAndCloseButtonsAreShown()
+
+ clearButton.click()
+ // Invoking the "Clear" action should remove all the custom actions and their corresponding
+ // buttons, ...
+ uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(TEST_APP_PIP_MENU_ACTION_ON)?.also {
+ isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu")
+ }
+ assertNull("\"Off\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_OFF))
+ assertNull("\"Clear\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_CLEAR))
+ assertNull("\"No-Op\" button should not be shown in Pip menu",
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_NO_OP))
+ // ... but the menu should still contain the "Full screen" and "Close" buttons.
+ assertFullscreenAndCloseButtonsAreShown()
+
+ testApp.closePipWindow()
+ }
+
+ @Test
+ fun pipMenu_customActions_override_mediaControls() {
+ // Start media session before entering PiP with custom actions.
+ testApp.clickStartMediaSessionButton()
+ testApp.checkWithCustomActionsCheckbox()
+ enterPip_openMenu_assertShown()
+
+ // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_NO_OP)
+ ?: fail("\"No-Op\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_OFF)
+ ?: fail("\"Off\" button should be shown in Pip menu")
+ val clearButton =
+ uiDevice.findTvPipMenuElementWithDescription(TEST_APP_PIP_MENU_ACTION_CLEAR)
+ ?: fail("\"Clear\" button should be shown in Pip menu")
+ // ... should also contain the "Full screen" and "Close" buttons, ...
+ assertFullscreenAndCloseButtonsAreShown()
+ // ... but should not contain media buttons.
+ assertNull("\"Play\" button should not be shown in menu when there are custom actions",
+ uiDevice.findTvPipMenuElementWithDescription(playButtonDescription))
+ assertNull("\"Pause\" button should not be shown in menu when there are custom actions",
+ uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription))
+
+ clearButton.click()
+ // Invoking the "Clear" action should remove all the custom actions, which should bring up
+ // media buttons...
+ uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription)
+ ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
+ "playing media session.")
+ // ... while the "Full screen" and "Close" buttons should remain in the menu.
+ assertFullscreenAndCloseButtonsAreShown()
+
+ testApp.closePipWindow()
+ }
+
private fun enterPip_openMenu_assertShown(): UiObject2 {
testApp.clickEnterPipButton()
// Pressing the Window key should bring up Pip menu
uiDevice.pressWindowKey()
return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown")
}
+
+ private fun assertFullscreenAndCloseButtonsAreShown() {
+ uiDevice.findTvPipMenuCloseButton()
+ ?: fail("\"Close PIP\" button should be shown in Pip menu")
+ uiDevice.findTvPipMenuFullscreenButton()
+ ?: fail("\"Full screen\" button should be shown in Pip menu")
+ }
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index 8db8bc67da14..0732794903b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -58,6 +58,9 @@ fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
?.findObject(buttonSelector)
}
+fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? =
+ wait(Until.gone(By.copy(tvPipMenuSelector).hasDescendant(By.desc(desc))), WAIT_TIME_MS)
+
fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
index a0056dfc0948..5570a562a515 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -24,10 +25,10 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.dockedStackDividerIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.wm.shell.flicker.navBarLayerIsAlwaysVisible
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
+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 org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,6 +39,7 @@ import org.junit.runners.Parameterized
* Test SplitScreen launch.
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
index 32e112dbd598..7c47d1f1b1ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ExitSplitScreenTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.Presubmit
import android.util.Rational
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -28,8 +29,8 @@ import com.android.server.wm.flicker.helpers.resizeSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.wm.shell.flicker.navBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,6 +41,7 @@ import org.junit.runners.Parameterized
* Test exit SplitScreen mode.
* To run this test: `atest WMShellFlickerTests:ExitSplitScreenTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt
new file mode 100644
index 000000000000..c85561d96091
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+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.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:OpenAppToSplitScreenTest`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToSplitScreenTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+
+ // b/161435597 causes the test not to work on 90 degrees
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("appToSplitScreen", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ test {
+ testApp.exit()
+ }
+ }
+ transitions {
+ device.launchSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation, enabled = false)
+ navBarLayerRotatesAndScales(configuration.endRotation,
+ bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.endRotation)
+
+ all("dividerLayerBecomesVisible") {
+ this.hidesLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .showsLayer(DOCKED_STACK_DIVIDER)
+ }
+ }
+
+ eventLog {
+ focusChanges(testApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ResizeSplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ResizeSplitScreenTest.kt
new file mode 100644
index 000000000000..7c83846621de
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/ResizeSplitScreenTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.splitscreen
+
+import android.graphics.Region
+import android.util.Rational
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.resizeSplitScreen
+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.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.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test split screen resizing window transitions.
+ * To run this test: `atest WMShellFlickerTests:ResizeSplitScreenTest`
+ *
+ * Currently it runs only in 0 degrees because of b/156100803
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 159096424)
+class ResizeSplitScreenTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ private const val sSimpleActivity = "SimpleActivity"
+ private const val sImeActivity = "ImeActivity"
+ private val startRatio = Rational(1, 3)
+ private val stopRatio = Rational(2, 3)
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testAppTop = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+ val testAppBottom = ImeAppHelper(instrumentation)
+
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName {
+ val description = (startRatio.toString().replace("/", "-") + "_to_" +
+ stopRatio.toString().replace("/", "-"))
+ buildTestTag("resizeSplitScreen", testAppTop.launcherName,
+ configuration.startRotation, configuration.endRotation,
+ testAppBottom.launcherName, description)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ this.launcherStrategy.clearRecentAppsFromOverview()
+ testAppBottom.open()
+ device.pressHome()
+ testAppTop.open()
+ device.waitForIdle()
+ device.launchSplitScreen()
+ val snapshot =
+ device.findObject(By.res(device.launcherPackageName, "snapshot"))
+ snapshot.click()
+ testAppBottom.openIME(device)
+ device.pressBack()
+ device.resizeSplitScreen(startRatio)
+ }
+ }
+ teardown {
+ eachRun {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ device.pressHome()
+ testAppTop.exit()
+ testAppBottom.exit()
+ }
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ device.resizeSplitScreen(stopRatio)
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+
+ all("topAppWindowIsAlwaysVisible", bugId = 156223549) {
+ this.showsAppWindow(sSimpleActivity)
+ }
+
+ all("bottomAppWindowIsAlwaysVisible", bugId = 156223549) {
+ this.showsAppWindow(sImeActivity)
+ }
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
+
+ all("topAppLayerIsAlwaysVisible") {
+ this.showsLayer(sSimpleActivity)
+ }
+
+ all("bottomAppLayerIsAlwaysVisible") {
+ this.showsLayer(sImeActivity)
+ }
+
+ all("dividerLayerIsAlwaysVisible") {
+ this.showsLayer(DOCKED_STACK_DIVIDER)
+ }
+
+ start("appsStartingBounds", enabled = false) {
+ val displayBounds = WindowUtils.displayBounds
+ val entry = this.trace.entries.firstOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerBounds =
+ entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+ val topAppBounds = Region(0, 0, dividerBounds.right,
+ dividerBounds.top + WindowUtils.dockedStackDividerInset)
+ val bottomAppBounds = Region(0,
+ dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.navigationBarHeight)
+ this.hasVisibleRegion("SimpleActivity", topAppBounds)
+ .and()
+ .hasVisibleRegion("ImeActivity", bottomAppBounds)
+ }
+
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.displayBounds
+ val entry = this.trace.entries.lastOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerBounds =
+ entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+ val topAppBounds = Region(0, 0, dividerBounds.right,
+ dividerBounds.top + WindowUtils.dockedStackDividerInset)
+ val bottomAppBounds = Region(0,
+ dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.navigationBarHeight)
+
+ this.hasVisibleRegion(sSimpleActivity, topAppBounds)
+ .and()
+ .hasVisibleRegion(sImeActivity, bottomAppBounds)
+ }
+ }
+
+ eventLog {
+ focusDoesNotChange()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt
new file mode 100644
index 000000000000..d2371bd766f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateOneLaunchedAppTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.splitscreen
+
+import androidx.test.filters.FlakyTest
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:SplitScreenRotateOneLaunchedAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest
+class SplitScreenRotateOneLaunchedAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+
+ return FlickerTestRunnerFactory(instrumentation, repetitions = 3)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenRotateOneApp", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ device.launchSplitScreen()
+ device.waitForIdle()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt
new file mode 100644
index 000000000000..67346424acd2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenRotateTwoLaunchedAppTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.splitscreen
+
+import androidx.test.filters.FlakyTest
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:SplitScreenRotateTwoLaunchedAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest
+class SplitScreenRotateTwoLaunchedAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+ val secondaryApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp",
+ "SplitScreenSecondaryApp")
+
+ return FlickerTestRunnerFactory(instrumentation, repetitions = 3)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenRotateTwoApps", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ device.pressHome()
+ secondaryApp.open()
+ device.pressHome()
+ device.launchSplitScreen()
+ device.reopenAppFromOverview()
+ device.waitForIdle()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ secondaryApp.exit()
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenTestBase.kt
index 496fe94ba951..a3440df9ddf8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenTestBase.kt
@@ -17,11 +17,10 @@
package com.android.wm.shell.flicker.splitscreen
import com.android.wm.shell.flicker.NonRotationTestBase
-import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
-import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_SECONDARY_LABEL
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.testapp.Components
abstract class SplitScreenTestBase(
rotationName: String,
@@ -29,8 +28,8 @@ abstract class SplitScreenTestBase(
) : NonRotationTestBase(rotationName, rotation) {
protected val splitScreenApp = SplitScreenHelper(instrumentation,
TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
- TEST_APP_SPLITSCREEN_PRIMARY_COMPONENT_NAME)
+ Components.SplitScreenActivity())
protected val secondaryApp = SplitScreenHelper(instrumentation,
TEST_APP_SPLITSCREEN_SECONDARY_LABEL,
- TEST_APP_SPLITSCREEN_SECONDARY_COMPONENT_NAME)
+ Components.SplitScreenSecondaryActivity())
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenToLauncherTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenToLauncherTest.kt
new file mode 100644
index 000000000000..00979fa9fac0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenToLauncherTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+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.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:SplitScreenToLauncherTest`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SplitScreenToLauncherTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.wm.shell.flicker.testapp", "SimpleApp")
+
+ // b/161435597 causes the test not to work on 90 degrees
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenToLauncher", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.endRotation)
+ device.launchSplitScreen()
+ device.waitForIdle()
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ }
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
+ device.exitSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
+
+ // b/161435597 causes the test not to work on 90 degrees
+ all("dividerLayerBecomesInvisible") {
+ this.showsLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .hidesLayer(DOCKED_STACK_DIVIDER)
+ }
+
+ all("appLayerBecomesInvisible") {
+ this.showsLayer(testApp.getPackage())
+ .then()
+ .hidesLayer(testApp.getPackage())
+ }
+ }
+
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
index d12b49245277..26627a47ee62 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
@@ -18,3 +18,9 @@ android_test {
sdk_version: "current",
test_suites: ["device-tests"],
}
+
+java_library {
+ name: "wmshell-flicker-test-components",
+ srcs: ["src/**/Components.java"],
+ sdk_version: "test_current",
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 2ce120448ac4..a583b725899b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -21,13 +21,25 @@
android:targetSdkVersion="29"/>
<application android:allowBackup="false"
android:supportsRtl="true">
+ <activity android:name=".FixedActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:launchMode="singleTop"
+ android:label="FixedApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".PipActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
- android:label="PipApp"
- android:exported="true">
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
+ android:launchMode="singleTop"
+ android:label="PipApp"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -39,9 +51,9 @@
</activity>
<activity android:name=".ImeActivity"
- android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
- android:label="ImeApp"
- android:exported="true">
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
+ android:label="ImeApp"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -73,5 +85,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+
+ <activity android:name=".SimpleActivity"
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
+ android:label="SimpleApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
index b4a4c165cc7b..e5d2f82080a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -28,6 +28,12 @@
android:text="Enter PIP"
android:onClick="enterPip"/>
+ <CheckBox
+ android:id="@+id/with_custom_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="With custom actions"/>
+
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
new file mode 100644
index 000000000000..5d94e5177dcc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_orange_light">
+
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
new file mode 100644
index 000000000000..8e9b4cb2d53e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
@@ -0,0 +1,82 @@
+/*
+ * 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.testapp;
+
+import android.content.ComponentName;
+
+public class Components {
+ public abstract static class ComponentsInfo {
+ public ComponentName getComponentName() {
+ return ComponentName.createRelative(PACKAGE_NAME, "." + getActivityName());
+ }
+ public abstract String getActivityName();
+ }
+
+ public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
+
+ public static class FixedActivity extends ComponentsInfo {
+ // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+ public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+
+ @Override
+ public String getActivityName() {
+ return FixedActivity.class.getSimpleName();
+ }
+ }
+
+ public static class PipActivity extends ComponentsInfo {
+ // Intent action that this activity dynamically registers to enter picture-in-picture
+ public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
+ // Intent action that this activity dynamically registers to set requested orientation.
+ // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+ public static final String ACTION_SET_REQUESTED_ORIENTATION =
+ PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
+
+ // Calls enterPictureInPicture() on creation
+ public static final String EXTRA_ENTER_PIP = "enter_pip";
+ // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+ public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
+ // Adds a click listener to finish this activity when it is clicked
+ public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+
+ @Override
+ public String getActivityName() {
+ return PipActivity.class.getSimpleName();
+ }
+ }
+
+ public static class ImeActivity extends ComponentsInfo {
+ @Override
+ public String getActivityName() {
+ return ImeActivity.class.getSimpleName();
+ }
+ }
+
+ public static class SplitScreenActivity extends ComponentsInfo {
+ @Override
+ public String getActivityName() {
+ return SplitScreenActivity.class.getSimpleName();
+ }
+ }
+
+ public static class SplitScreenSecondaryActivity extends ComponentsInfo {
+ @Override
+ public String getActivityName() {
+ return SplitScreenSecondaryActivity.class.getSimpleName();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
new file mode 100644
index 000000000000..d4ae6c1313bf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.testapp;
+
+import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
+
+import android.os.Bundle;
+
+public class FixedActivity extends SimpleActivity {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Set the fixed orientation if requested
+ if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
+ final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
+ setRequestedOrientation(ori);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
index d2fcd0d31558..a6ba7823e22d 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -24,18 +24,38 @@ import static android.media.session.PlaybackState.STATE_PAUSED;
import static android.media.session.PlaybackState.STATE_PLAYING;
import static android.media.session.PlaybackState.STATE_STOPPED;
+import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
+import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
+import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
+import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
+
import android.app.Activity;
+import android.app.PendingIntent;
import android.app.PictureInPictureParams;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.util.Log;
import android.util.Rational;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
public class PipActivity extends Activity {
+ private static final String TAG = PipActivity.class.getSimpleName();
/**
* A media session title for when the session is in {@link STATE_PLAYING}.
* TvPipNotificationTests check whether the actual notification title matches this string.
@@ -52,7 +72,19 @@ public class PipActivity extends Activity {
private static final Rational RATIO_WIDE = new Rational(2, 1);
private static final Rational RATIO_TALL = new Rational(1, 2);
- private PictureInPictureParams.Builder mPipParamsBuilder;
+ private static final String PIP_ACTION_NO_OP = "No-Op";
+ private static final String PIP_ACTION_OFF = "Off";
+ private static final String PIP_ACTION_ON = "On";
+ private static final String PIP_ACTION_CLEAR = "Clear";
+ private static final String ACTION_NO_OP = "com.android.wm.shell.flicker.testapp.NO_OP";
+ private static final String ACTION_SWITCH_OFF =
+ "com.android.wm.shell.flicker.testapp.SWITCH_OFF";
+ private static final String ACTION_SWITCH_ON = "com.android.wm.shell.flicker.testapp.SWITCH_ON";
+ private static final String ACTION_CLEAR = "com.android.wm.shell.flicker.testapp.CLEAR";
+
+ private final PictureInPictureParams.Builder mPipParamsBuilder =
+ new PictureInPictureParams.Builder()
+ .setAspectRatio(RATIO_DEFAULT);
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
@@ -60,6 +92,46 @@ public class PipActivity extends Activity {
private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
+ private final List<RemoteAction> mSwitchOffActions = new ArrayList<>();
+ private final List<RemoteAction> mSwitchOnActions = new ArrayList<>();
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isInPictureInPictureMode()) {
+ switch (intent.getAction()) {
+ case ACTION_SWITCH_ON:
+ mPipParamsBuilder.setActions(mSwitchOnActions);
+ break;
+ case ACTION_SWITCH_OFF:
+ mPipParamsBuilder.setActions(mSwitchOffActions);
+ break;
+ case ACTION_CLEAR:
+ mPipParamsBuilder.setActions(Collections.emptyList());
+ break;
+ case ACTION_NO_OP:
+ return;
+ default:
+ Log.w(TAG, "Unhandled action=" + intent.getAction());
+ return;
+ }
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ } else {
+ switch (intent.getAction()) {
+ case ACTION_ENTER_PIP:
+ enterPip(null);
+ break;
+ case ACTION_SET_REQUESTED_ORIENTATION:
+ setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
+ EXTRA_PIP_ORIENTATION)));
+ break;
+ default:
+ Log.w(TAG, "Unhandled action=" + intent.getAction());
+ return;
+ }
+ }
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -72,9 +144,6 @@ public class PipActivity extends Activity {
setContentView(R.layout.activity_pip);
- mPipParamsBuilder = new PictureInPictureParams.Builder()
- .setAspectRatio(RATIO_DEFAULT);
-
findViewById(R.id.media_session_start)
.setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING));
findViewById(R.id.media_session_stop)
@@ -98,9 +167,52 @@ public class PipActivity extends Activity {
updateMediaSessionState(STATE_STOPPED);
}
});
+
+ // Build two sets of the custom actions. We'll replace one with the other when 'On'/'Off'
+ // action is invoked.
+ // The first set consists of 3 actions: 1) Off; 2) No-Op; 3) Clear.
+ // The second set consists of 2 actions: 1) On; 2) Clear.
+ // Upon invocation 'Clear' action clear-off all the custom actions, including itself.
+ final Icon icon = Icon.createWithResource(this, android.R.drawable.ic_menu_help);
+ final RemoteAction noOpAction = buildRemoteAction(icon, PIP_ACTION_NO_OP, ACTION_NO_OP);
+ final RemoteAction switchOnAction =
+ buildRemoteAction(icon, PIP_ACTION_ON, ACTION_SWITCH_ON);
+ final RemoteAction switchOffAction =
+ buildRemoteAction(icon, PIP_ACTION_OFF, ACTION_SWITCH_OFF);
+ final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR);
+ mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction));
+ mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction));
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_NO_OP);
+ filter.addAction(ACTION_SWITCH_ON);
+ filter.addAction(ACTION_SWITCH_OFF);
+ filter.addAction(ACTION_CLEAR);
+ filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
+ filter.addAction(ACTION_ENTER_PIP);
+ registerReceiver(mBroadcastReceiver, filter);
+
+ handleIntentExtra(getIntent());
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
+ final Intent intent = new Intent(action);
+ final PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ return new RemoteAction(icon, label, label, pendingIntent);
}
public void enterPip(View v) {
+ final boolean withCustomActions =
+ ((CheckBox) findViewById(R.id.with_custom_actions)).isChecked();
+ mPipParamsBuilder.setActions(
+ withCustomActions ? mSwitchOnActions : Collections.emptyList());
enterPictureInPictureMode(mPipParamsBuilder.build());
}
@@ -153,4 +265,17 @@ public class PipActivity extends Activity {
mMediaSession.setMetadata(mMediaMetadataBuilder.build());
mMediaSession.setActive(newState != STATE_STOPPED);
}
+
+ private void handleIntentExtra(Intent intent) {
+ // Set the fixed orientation if requested
+ if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) {
+ final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
+ setRequestedOrientation(ori);
+ }
+ // Enter picture in picture with the given aspect ratio if provided
+ if (intent.hasExtra(EXTRA_ENTER_PIP)) {
+ mPipParamsBuilder.setActions(mSwitchOnActions);
+ enterPip(null);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
new file mode 100644
index 000000000000..5343c1893d4e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class SimpleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.activity_simple);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index fdf4d31f0281..3ff750af7ec9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -108,8 +108,7 @@ public class ShellTaskOrganizerTests {
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
- mTransactionPool, mTestExecutor, mTestExecutor, mContext));
+ mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
index 754f73246c86..8dbc1d56bcc2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,19 +51,17 @@ public class AppPairTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController,
- mTaskStackListener);
when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
when(mDisplayController.getDisplay(anyInt())).thenReturn(
mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+ mController = new TestAppPairsController(
+ mTaskOrganizer,
+ mSyncQueue,
+ mDisplayController);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
index 6d441ab898ec..fada694a4c07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -52,20 +51,18 @@ public class AppPairsControllerTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
+ when(mDisplayController.getDisplay(anyInt())).thenReturn(
+ mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
- mDisplayController,
- mTaskStackListener);
+ mDisplayController);
mPool = mController.getPool();
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
index d3dbbfe37985..a3f134ee97ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
@@ -18,13 +18,16 @@ package com.android.wm.shell.apppairs;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.After;
import org.junit.Before;
@@ -36,22 +39,21 @@ import org.mockito.MockitoAnnotations;
/** Tests for {@link AppPairsPool} */
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AppPairsPoolTests {
+public class AppPairsPoolTests extends ShellTestCase {
private TestAppPairsController mController;
private TestAppPairsPool mPool;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController;
- @Mock private TaskStackListenerImpl mTaskStackListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
mController = new TestAppPairsController(
mTaskOrganizer,
mSyncQueue,
- mDisplayController,
- mTaskStackListener);
+ mDisplayController);
mPool = mController.getPool();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
index e61cc91c394b..be0963628933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
@@ -19,14 +19,13 @@ package com.android.wm.shell.apppairs;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
public class TestAppPairsController extends AppPairsController {
TestAppPairsPool mPool;
public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, TaskStackListenerImpl taskStackListener) {
- super(organizer, syncQueue, displayController, taskStackListener);
+ DisplayController displayController) {
+ super(organizer, syncQueue, displayController);
mPool = new TestAppPairsPool(this);
setPairsPool(mPool);
}
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 080cddc58a09..5e0d51809d44 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
@@ -20,12 +20,15 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.Surface.ROTATION_0;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.graphics.Point;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -68,19 +71,31 @@ public class DisplayImeControllerTest {
@Test
public void reappliesVisibilityToChangedLeash() {
verifyZeroInteractions(mT);
+ mPerDisplay.mImeShowing = true;
- mPerDisplay.mImeShowing = false;
- mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
- });
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+ assertFalse(mPerDisplay.mImeShowing);
verify(mT).hide(any());
mPerDisplay.mImeShowing = true;
- mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
- });
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(true), insetsSourceControl());
+ assertTrue(mPerDisplay.mImeShowing);
verify(mT).show(any());
}
+
+ private InsetsSourceControl[] insetsSourceControl() {
+ return new InsetsSourceControl[]{
+ new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ };
+ }
+
+ private InsetsState insetsStateWithIme(boolean visible) {
+ InsetsState state = new InsetsState();
+ state.addSource(new InsetsSource(ITYPE_IME));
+ state.setSourceVisible(ITYPE_IME, visible);
+ return state;
+ }
+
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
new file mode 100644
index 000000000000..d87f4c60fad4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -0,0 +1,106 @@
+/*
+ * 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.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link SplitLayout} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitLayoutTests extends ShellTestCase {
+ @Mock SplitLayout.LayoutChangeListener mLayoutChangeListener;
+ @Mock SurfaceControl mRootLeash;
+ private SplitLayout mSplitLayout;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mSplitLayout = new SplitLayout(
+ mContext,
+ getConfiguration(false),
+ mLayoutChangeListener,
+ mRootLeash);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUpdateConfiguration() {
+ assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse();
+ assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue();
+ }
+
+ @Test
+ public void testUpdateDividePosition() {
+ mSplitLayout.updateDividePosition(anyInt());
+ verify(mLayoutChangeListener).onBoundsChanging(any(SplitLayout.class));
+ }
+
+ @Test
+ public void testSetSnapTarget() {
+ DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0,
+ DividerSnapAlgorithm.SnapTarget.FLAG_NONE);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onBoundsChanged(any(SplitLayout.class));
+
+ // verify it callbacks properly when the snap target indicates dismissing split.
+ snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onSnappedToDismiss(eq(false));
+ snapTarget = getSnapTarget(0, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+ mSplitLayout.setSnapTarget(snapTarget);
+ verify(mLayoutChangeListener).onSnappedToDismiss(eq(true));
+ }
+
+ private static Configuration getConfiguration(boolean isLandscape) {
+ final Configuration configuration = new Configuration();
+ configuration.unset();
+ configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ configuration.windowConfiguration.setBounds(
+ new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160));
+ return configuration;
+ }
+
+ private static DividerSnapAlgorithm.SnapTarget getSnapTarget(int position, int flag) {
+ return new DividerSnapAlgorithm.SnapTarget(
+ position /* position */, position /* taskPosition */, flag);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
new file mode 100644
index 000000000000..aa0eb2f95ed8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.common.split;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link SplitWindowManager} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitWindowManagerTests extends ShellTestCase {
+ @Mock SurfaceControl mSurfaceControl;
+ @Mock SplitLayout mSplitLayout;
+ private SplitWindowManager mSplitWindowManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ final Configuration configuration = new Configuration();
+ configuration.setToDefaults();
+ mSplitWindowManager = new SplitWindowManager(mContext, configuration, mSurfaceControl);
+ when(mSplitLayout.getDividerBounds()).thenReturn(
+ new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
+ configuration.windowConfiguration.getBounds().height()));
+ }
+
+ @Test
+ @UiThreadTest
+ public void testInitRelease() {
+ mSplitWindowManager.init(mSplitLayout);
+ assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
+ mSplitWindowManager.release();
+ assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
+ }
+}
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 fad1f057267a..92d4bee7cfc2 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
@@ -42,7 +42,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
-import android.app.IActivityTaskManager;
+import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -69,7 +69,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@@ -88,7 +87,7 @@ public class DragAndDropPolicyTest {
private Context mContext;
@Mock
- private IActivityTaskManager mIActivityTaskManager;
+ private ActivityTaskManager mActivityTaskManager;
@Mock
private SplitScreen mSplitScreen;
@@ -134,7 +133,7 @@ public class DragAndDropPolicyTest {
return null;
}).when(mSplitScreen).registerInSplitScreenListener(any());
- mPolicy = new DragAndDropPolicy(mContext, mIActivityTaskManager, mSplitScreen, mStarter);
+ mPolicy = new DragAndDropPolicy(mContext, mActivityTaskManager, mSplitScreen, mStarter);
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -188,9 +187,9 @@ public class DragAndDropPolicyTest {
return info;
}
- private void setRunningTask(ActivityManager.RunningTaskInfo task) throws RemoteException {
- doReturn(Collections.singletonList(task)).when(mIActivityTaskManager)
- .getFilteredTasks(anyInt(), anyBoolean());
+ private void setRunningTask(ActivityManager.RunningTaskInfo task) {
+ doReturn(Collections.singletonList(task)).when(mActivityTaskManager)
+ .getTasks(anyInt(), anyBoolean());
}
private void setClipDataResizeable(ClipData data, boolean resizeable) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
index 0f719afd08b3..5cbc7d927d61 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
@@ -96,9 +96,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(75, 0, 125, 75),
- /* taskBounds */ new Rect(50, 0, 125, 100)),
+ /* taskBounds */ new Rect(50, 0, 125, 100),
+ /* activityInsets */ new Rect(0, 0, 0, 0)),
mLeash);
// Task doesn't need to repositioned
@@ -109,10 +111,12 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskInfoChanged(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
// Activity is offset by 25 to the left
/* activityBounds */ new Rect(50, 0, 100, 75),
- /* taskBounds */ new Rect(50, 0, 125, 100)));
+ /* taskBounds */ new Rect(50, 0, 125, 100),
+ /* activityInsets */ new Rect(0, 0, 0, 0)));
// Task needs to be repositioned by 25 to the left
verifySetPosition(75, 0);
@@ -130,9 +134,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(150, 0, 200, 75),
- /* taskBounds */ new Rect(125, 0, 200, 100)),
+ /* taskBounds */ new Rect(125, 0, 200, 100),
+ /* activityInsets */ new Rect(0, 10, 10, 0)),
mLeash);
verifySetPosition(-15, 0);
@@ -150,9 +156,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(150, 0, 200, 75),
- /* taskBounds */ new Rect(125, 0, 200, 100)),
+ /* taskBounds */ new Rect(125, 0, 200, 100),
+ /* activityInsets */ new Rect(0, 10, 10, 0)),
mLeash);
verifySetPosition(55, 0);
@@ -170,9 +178,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 100),
/* activityBounds */ new Rect(50, 0, 100, 75),
- /* taskBounds */ new Rect(25, 0, 100, 100)),
+ /* taskBounds */ new Rect(25, 0, 100, 100),
+ /* activityInsets */ new Rect(0, 10, 10, 0)),
mLeash);
verifySetPosition(115, 0);
@@ -190,9 +200,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
- /* taskBounds */ new Rect(0, 50, 100, 125)),
+ /* taskBounds */ new Rect(0, 50, 100, 125),
+ /* activityInsets */ new Rect(10, 0, 0, 0)),
mLeash);
verifySetPosition(20, -15);
@@ -210,9 +222,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
- /* taskBounds */ new Rect(0, 50, 100, 125)),
+ /* taskBounds */ new Rect(0, 50, 100, 125),
+ /* activityInsets */ new Rect(10, 0, 0, 0)),
mLeash);
verifySetPosition(20, 20);
@@ -221,6 +235,29 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
}
@Test
+ public void testOnTaskInfoAppeared_portraitWithCenterGravity_visibleLeftInset() {
+ mLetterboxConfigController.setPortraitGravity(Gravity.CENTER);
+ setWindowBoundsAndInsets(
+ /* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
+ Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20));
+
+ mLetterboxTaskListener.onTaskAppeared(
+ createTaskInfo(
+ /* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
+ /* parentBounds */ new Rect(0, 0, 100, 150),
+ /* activityBounds */ new Rect(0, 75, 50, 125),
+ /* taskBounds */ new Rect(0, 50, 100, 125),
+ // Activity is drawn under the left inset.
+ /* activityInsets */ new Rect(0, 0, 0, 0)),
+ mLeash);
+
+ verifySetPosition(20, 20);
+ // Should return activity coordinates offset by task coordinates
+ verifySetWindowCrop(new Rect(0, 25, 50, 75));
+ }
+
+ @Test
public void testOnTaskInfoAppeared_portraitWithBottomGravity() {
mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM);
setWindowBoundsAndInsets(
@@ -230,9 +267,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 100, 150),
/* activityBounds */ new Rect(0, 75, 50, 125),
- /* taskBounds */ new Rect(0, 50, 100, 125)),
+ /* taskBounds */ new Rect(0, 50, 100, 125),
+ /* activityInsets */ new Rect(10, 0, 0, 0)),
mLeash);
verifySetPosition(20, 55);
@@ -250,16 +289,17 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds
/* parentBounds */ new Rect(0, 0, 200, 125),
/* activityBounds */ new Rect(15, 0, 175, 120),
- /* taskBounds */ new Rect(0, 0, 100, 125)), // equal to parent bounds
+ /* taskBounds */ new Rect(0, 0, 200, 125),
+ /* activityInsets */ new Rect(10, 25, 10, 10)), // equal to parent bounds
mLeash);
// Activity fully covers parent bounds with insets so doesn't need to be moved.
verifySetPosition(0, 0);
- // Should return activity coordinates offset by task coordinates minus all insets
- // except top one (keep status bar decor visible).
- verifySetWindowCrop(new Rect(25, 0, 165, 110));
+ // Should return activity coordinates offset by task coordinates
+ verifySetWindowCrop(new Rect(15, 0, 175, 120));
}
@Test
@@ -272,9 +312,11 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
mLetterboxTaskListener.onTaskAppeared(
createTaskInfo(
/* taskId */ 1,
+ /* maxBounds= */ new Rect(0, 0, 100, 150),
/* parentBounds */ new Rect(0, 75, 100, 225),
/* activityBounds */ new Rect(25, 75, 75, 125),
- /* taskBounds */ new Rect(0, 75, 100, 125)),
+ /* taskBounds */ new Rect(0, 75, 100, 125),
+ /* activityInsets */ new Rect(10, 0, 0, 0)),
mLeash);
verifySetPosition(0, 0);
@@ -285,7 +327,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
setWindowBoundsAndInsets(new Rect(), Insets.NONE);
RunningTaskInfo taskInfo =
- createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect());
+ createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect(), new Rect(),
+ new Rect());
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
}
@@ -306,14 +349,18 @@ public final class LetterboxTaskListenerTest extends ShellTestCase {
private static RunningTaskInfo createTaskInfo(
int taskId,
+ final Rect maxBounds,
final Rect parentBounds,
final Rect activityBounds,
- final Rect taskBounds) {
+ final Rect taskBounds,
+ final Rect activityInsets) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setMaxBounds(maxBounds);
taskInfo.parentBounds = parentBounds;
taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
+ taskInfo.letterboxActivityInsets = Rect.copyOrNull(activityInsets);
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 55e7a354f4cd..7f280cd124d2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -79,7 +79,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect(), null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null,
+ TRANSITION_DIRECTION_TO_PIP);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -87,16 +88,19 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void getAnimator_whenSameTypeRunning_updateExistingAnimator() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ TRANSITION_DIRECTION_TO_PIP);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue2, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue2, null,
+ TRANSITION_DIRECTION_TO_PIP);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -122,11 +126,13 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
@SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+ TRANSITION_DIRECTION_TO_PIP);
animator.updateEndValue(endValue2);
@@ -135,10 +141,12 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void pipTransitionAnimator_setPipAnimationCallback() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue, null, TRANSITION_DIRECTION_TO_PIP);
+ .getAnimator(mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index a65d832359d2..ef9923550fc5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -337,7 +337,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
reentryBounds.scale(1.25f);
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(reentryBounds, reentrySnapFraction);
+ mPipBoundsState.saveReentryState(
+ new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
assertEquals(reentryBounds.width(), destinationBounds.width());
@@ -351,7 +352,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
reentryBounds.offset(0, -100);
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(reentryBounds, reentrySnapFraction);
+ mPipBoundsState.saveReentryState(
+ new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 4bcca06b592f..8ba301a9ebfa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -47,7 +47,7 @@ import java.util.function.BiConsumer;
@SmallTest
public class PipBoundsStateTest extends ShellTestCase {
- private static final Rect DEFAULT_BOUNDS = new Rect(0, 0, 10, 10);
+ private static final Size DEFAULT_SIZE = new Size(10, 10);
private static final float DEFAULT_SNAP_FRACTION = 1.0f;
private PipBoundsState mPipBoundsState;
@@ -71,22 +71,22 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetReentryState() {
- final Rect bounds = new Rect(0, 0, 100, 100);
+ final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(bounds, snapFraction);
+ mPipBoundsState.saveReentryState(size, snapFraction);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
- assertEquals(new Size(100, 100), state.getSize());
+ assertEquals(size, state.getSize());
assertEquals(snapFraction, state.getSnapFraction(), 0.01);
}
@Test
public void testClearReentryState() {
- final Rect bounds = new Rect(0, 0, 100, 100);
+ final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(bounds, snapFraction);
+ mPipBoundsState.saveReentryState(size, snapFraction);
mPipBoundsState.clearReentryState();
assertNull(mPipBoundsState.getReentryState());
@@ -95,20 +95,20 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_BOUNDS, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
assertNotNull(state);
- assertEquals(new Size(DEFAULT_BOUNDS.width(), DEFAULT_BOUNDS.height()), state.getSize());
+ assertEquals(DEFAULT_SIZE, state.getSize());
assertEquals(DEFAULT_SNAP_FRACTION, state.getSnapFraction(), 0.01);
}
@Test
public void testSetLastPipComponentName_changed_clearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_BOUNDS, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 8d3774cee1e0..45e4241d5bc6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,7 +44,7 @@ import android.window.WindowContainerToken;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
@@ -66,7 +66,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private DisplayController mMockdDisplayController;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
- @Mock private PipMenuActivityController mMenuActivityController;
+ @Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
@@ -83,9 +83,9 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext);
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
- mMockPipBoundsAlgorithm, mMenuActivityController, mMockPipSurfaceTransactionHelper,
- mMockOptionalSplitScreen, mMockdDisplayController, mMockPipUiEventLogger,
- mMockShellTaskOrganizer));
+ mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
+ mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
+ mMockPipUiEventLogger, mMockShellTaskOrganizer));
preparePipTaskOrg();
}
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 88c8eb902a6f..62ffac4fbd3f 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
@@ -30,10 +30,12 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Size;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -61,7 +63,7 @@ public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
@Mock private DisplayController mMockDisplayController;
- @Mock private PipMenuActivityController mMockPipMenuActivityController;
+ @Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@@ -77,7 +79,7 @@ public class PipControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mPipController = new PipController(mContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockExecutor);
doAnswer(invocation -> {
@@ -110,7 +112,7 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
mMockExecutor));
}
@@ -135,4 +137,28 @@ public class PipControllerTest extends ShellTestCase {
verify(mMockPipBoundsState, never()).setLastPipComponentName(null);
}
+
+ @Test
+ public void saveReentryState_noUserResize_doesNotSaveSize() {
+ final Rect bounds = new Rect(0, 0, 10, 10);
+ when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
+ when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(false);
+
+ mPipController.saveReentryState(bounds);
+
+ verify(mMockPipBoundsState).saveReentryState(null, 1.0f);
+ }
+
+ @Test
+ public void saveReentryState_userHasResized_savesSize() {
+ final Rect bounds = new Rect(0, 0, 10, 10);
+ final Rect resizedBounds = new Rect(0, 0, 30, 30);
+ when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
+ when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
+ when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
+
+ mPipController.saveReentryState(bounds);
+
+ verify(mMockPipBoundsState).saveReentryState(new Size(30, 30), 1.0f);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index abbc681f53fe..4efaebf96c2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -60,7 +61,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipTouchHandler mPipTouchHandler;
@Mock
- private PipMenuActivityController mPipMenuActivityController;
+ private PhonePipMenuController mPhonePipMenuController;
@Mock
private PipTaskOrganizer mPipTaskOrganizer;
@@ -71,6 +72,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Mock
private PipUiEventLogger mPipUiEventLogger;
+ @Mock
+ private ShellExecutor mShellMainExecutor;
+
private PipBoundsState mPipBoundsState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSnapAlgorithm mPipSnapAlgorithm;
@@ -92,9 +96,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
mPipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
mPipSnapAlgorithm = new PipSnapAlgorithm();
- mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
+ mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
- mFloatingContentCoordinator, mPipUiEventLogger);
+ mFloatingContentCoordinator, mPipUiEventLogger, mShellMainExecutor);
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4ed5457a2a7f..0533aa6ad45d 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -333,6 +333,7 @@ cc_defaults {
"jni/YuvToJpegEncoder.cpp",
"jni/fonts/Font.cpp",
"jni/fonts/FontFamily.cpp",
+ "jni/fonts/NativeFont.cpp",
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
"jni/text/TextShaper.cpp",
@@ -429,6 +430,7 @@ cc_defaults {
whole_static_libs: ["libskia"],
srcs: [
+ "canvas/CanvasFrontend.cpp",
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
@@ -607,6 +609,7 @@ cc_test {
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasOpTests.cpp",
+ "tests/unit/CanvasFrontendTests.cpp",
"tests/unit/CommonPoolTests.cpp",
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
@@ -669,6 +672,7 @@ cc_benchmark {
srcs: [
"tests/microbench/main.cpp",
+ "tests/microbench/CanvasOpBench.cpp",
"tests/microbench/DisplayListCanvasBench.cpp",
"tests/microbench/LinearAllocatorBench.cpp",
"tests/microbench/PathParserBench.cpp",
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index dc63e5db4a70..dd2476313b19 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -16,14 +16,15 @@
#pragma once
-#include "pipeline/skia/SkiaDisplayList.h"
-
namespace android {
namespace uirenderer {
namespace VectorDrawable {
class Tree;
};
+namespace skiapipeline {
+class SkiaDisplayList;
+}
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
/**
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 49817925d9b4..c6c4ba8a6493 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -19,7 +19,6 @@ X(Save)
X(Restore)
X(SaveLayer)
X(SaveBehind)
-X(Concat44)
X(Concat)
X(SetMatrix)
X(Scale)
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index fd18d2f9192d..8b20492543f7 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -20,7 +20,7 @@
namespace android {
namespace uirenderer {
-const std::string FrameInfoNames[] = {
+const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames = {
"Flags",
"FrameTimelineVsyncId",
"IntendedVsync",
@@ -42,10 +42,6 @@ const std::string FrameInfoNames[] = {
"GpuCompleted",
};
-static_assert((sizeof(FrameInfoNames) / sizeof(FrameInfoNames[0])) ==
- static_cast<int>(FrameInfoIndex::NumIndexes),
- "size mismatch: FrameInfoNames doesn't match the enum!");
-
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index bb875e35f6f7..ee7d15a2fbce 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -21,6 +21,7 @@
#include <cutils/compiler.h>
#include <utils/Timers.h>
+#include <array>
#include <memory.h>
#include <string>
@@ -60,7 +61,7 @@ enum class FrameInfoIndex {
NumIndexes
};
-extern const std::string FrameInfoNames[];
+extern const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames;
namespace FrameInfoFlags {
enum {
@@ -158,7 +159,7 @@ public:
// GPU start time is approximated to the moment before swapBuffer is invoked.
// We could add an EGLSyncKHR fence at the beginning of the frame, but that is an overhead.
int64_t endTime = get(FrameInfoIndex::GpuCompleted);
- return endTime > 0 ? endTime - get(FrameInfoIndex::SwapBuffers) : 0;
+ return endTime > 0 ? endTime - get(FrameInfoIndex::SwapBuffers) : -1;
}
inline int64_t& set(FrameInfoIndex index) { return mFrameInfo[static_cast<int>(index)]; }
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index c174c240ff22..f4c633fbe58f 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -18,6 +18,7 @@
#include "renderstate/RenderState.h"
#include "utils/Color.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -52,5 +53,90 @@ SkBlendMode Layer::getMode() const {
}
}
+static inline SkScalar isIntegerAligned(SkScalar x) {
+ return fabsf(roundf(x) - x) <= NON_ZERO_EPSILON;
+}
+
+// Disable filtering when there is no scaling in screen coordinates and the corners have the same
+// fraction (for translate) or zero fraction (for any other rect-to-rect transform).
+static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, const SkRect& dstRect) {
+ if (!matrix.rectStaysRect()) return true;
+ SkRect dstDevRect = matrix.mapRect(dstRect);
+ float dstW, dstH;
+ if (MathUtils::isZero(matrix.getScaleX()) && MathUtils::isZero(matrix.getScaleY())) {
+ // Has a 90 or 270 degree rotation, although total matrix may also have scale factors
+ // in m10 and m01. Those scalings are automatically handled by mapRect so comparing
+ // dimensions is sufficient, but swap width and height comparison.
+ dstW = dstDevRect.height();
+ dstH = dstDevRect.width();
+ } else {
+ // Handle H/V flips or 180 rotation matrices. Axes may have been mirrored, but
+ // dimensions are still safe to compare directly.
+ dstW = dstDevRect.width();
+ dstH = dstDevRect.height();
+ }
+ if (!(MathUtils::areEqual(dstW, srcRect.width()) &&
+ MathUtils::areEqual(dstH, srcRect.height()))) {
+ return true;
+ }
+ // Device rect and source rect should be integer aligned to ensure there's no difference
+ // in how nearest-neighbor sampling is resolved.
+ return !(isIntegerAligned(srcRect.x()) &&
+ isIntegerAligned(srcRect.y()) &&
+ isIntegerAligned(dstDevRect.x()) &&
+ isIntegerAligned(dstDevRect.y()));
+}
+
+void Layer::draw(SkCanvas* canvas) {
+ GrRecordingContext* context = canvas->recordingContext();
+ if (context == nullptr) {
+ SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+ return;
+ }
+ SkMatrix layerTransform = getTransform();
+ //sk_sp<SkImage> layerImage = getImage();
+ const int layerWidth = getWidth();
+ const int layerHeight = getHeight();
+ if (layerImage) {
+ SkMatrix textureMatrixInv;
+ textureMatrixInv = getTexTransform();
+ // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+ // use bottom left origin and remove flipV and invert transformations.
+ SkMatrix flipV;
+ flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+ textureMatrixInv.preConcat(flipV);
+ textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight);
+ textureMatrixInv.postScale(layerImage->width(), layerImage->height());
+ SkMatrix textureMatrix;
+ if (!textureMatrixInv.invert(&textureMatrix)) {
+ textureMatrix = textureMatrixInv;
+ }
+
+ SkMatrix matrix;
+ matrix = SkMatrix::Concat(layerTransform, textureMatrix);
+
+ SkPaint paint;
+ paint.setAlpha(getAlpha());
+ paint.setBlendMode(getMode());
+ paint.setColorFilter(getColorFilter());
+ const bool nonIdentityMatrix = !matrix.isIdentity();
+ if (nonIdentityMatrix) {
+ canvas->save();
+ canvas->concat(matrix);
+ }
+ const SkMatrix& totalMatrix = canvas->getTotalMatrix();
+
+ SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
+ if (getForceFilter() || shouldFilterRect(totalMatrix, imageRect, imageRect)) {
+ paint.setFilterQuality(kLow_SkFilterQuality);
+ }
+ canvas->drawImage(layerImage.get(), 0, 0, &paint);
+ // restore the original matrix
+ if (nonIdentityMatrix) {
+ canvas->restore();
+ }
+ }
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index ea3bfc9e80cb..e99e76299317 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -21,6 +21,7 @@
#include <SkBlendMode.h>
#include <SkColorFilter.h>
#include <SkColorSpace.h>
+#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkImage.h>
#include <SkMatrix.h>
@@ -87,6 +88,8 @@ public:
inline sk_sp<SkImage> getImage() const { return this->layerImage; }
+ void draw(SkCanvas* canvas);
+
protected:
RenderState& mRenderState;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 473dc53dc4bf..a495ec4ac411 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -125,24 +125,18 @@ struct SaveBehind final : Op {
}
};
-struct Concat44 final : Op {
- static const auto kType = Type::Concat44;
- Concat44(const SkM44& m) : matrix(m) {}
- SkM44 matrix;
- void draw(SkCanvas* c, const SkMatrix&) const { c->concat(matrix); }
-};
struct Concat final : Op {
static const auto kType = Type::Concat;
- Concat(const SkMatrix& matrix) : matrix(matrix) {}
- SkMatrix matrix;
+ Concat(const SkM44& matrix) : matrix(matrix) {}
+ SkM44 matrix;
void draw(SkCanvas* c, const SkMatrix&) const { c->concat(matrix); }
};
struct SetMatrix final : Op {
static const auto kType = Type::SetMatrix;
- SetMatrix(const SkMatrix& matrix) : matrix(matrix) {}
- SkMatrix matrix;
+ SetMatrix(const SkM44& matrix) : matrix(matrix) {}
+ SkM44 matrix;
void draw(SkCanvas* c, const SkMatrix& original) const {
- c->setMatrix(SkMatrix::Concat(original, matrix));
+ c->setMatrix(SkM44(original) * matrix);
}
};
struct Scale final : Op {
@@ -569,12 +563,9 @@ void DisplayListData::saveBehind(const SkRect* subset) {
}
void DisplayListData::concat(const SkM44& m) {
- this->push<Concat44>(0, m);
-}
-void DisplayListData::concat(const SkMatrix& matrix) {
- this->push<Concat>(0, matrix);
+ this->push<Concat>(0, m);
}
-void DisplayListData::setMatrix(const SkMatrix& matrix) {
+void DisplayListData::setMatrix(const SkM44& matrix) {
this->push<SetMatrix>(0, matrix);
}
void DisplayListData::scale(SkScalar sx, SkScalar sy) {
@@ -834,10 +825,7 @@ bool RecordingCanvas::onDoSaveBehind(const SkRect* subset) {
void RecordingCanvas::didConcat44(const SkM44& m) {
fDL->concat(m);
}
-void RecordingCanvas::didConcat(const SkMatrix& matrix) {
- fDL->concat(matrix);
-}
-void RecordingCanvas::didSetMatrix(const SkMatrix& matrix) {
+void RecordingCanvas::didSetM44(const SkM44& matrix) {
fDL->setMatrix(matrix);
}
void RecordingCanvas::didScale(SkScalar sx, SkScalar sy) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 63d120c4ca19..4851148cd4d8 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -82,8 +82,7 @@ private:
void restore();
void concat(const SkM44&);
- void concat(const SkMatrix&);
- void setMatrix(const SkMatrix&);
+ void setMatrix(const SkM44&);
void scale(SkScalar, SkScalar);
void translate(SkScalar, SkScalar);
void translateZ(SkScalar);
@@ -154,8 +153,7 @@ public:
void onFlush() override;
void didConcat44(const SkM44&) override;
- void didConcat(const SkMatrix&) override;
- void didSetMatrix(const SkMatrix&) override;
+ void didSetM44(const SkM44&) override;
void didScale(SkScalar, SkScalar) override;
void didTranslate(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 31e45558139d..74c70c8969f2 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -380,7 +380,7 @@ void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) {
if (mDisplayList) {
mDisplayList->updateChildren(
[&observer, info](RenderNode* child) { child->decParentRefCount(observer, info); });
- if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) {
+ if (!mDisplayList->reuseDisplayList(this)) {
delete mDisplayList;
}
}
diff --git a/libs/hwui/SaveFlags.h b/libs/hwui/SaveFlags.h
new file mode 100644
index 000000000000..f3579a8e9f19
--- /dev/null
+++ b/libs/hwui/SaveFlags.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+// TODO: Move this to an enum class
+namespace android::SaveFlags {
+
+// These must match the corresponding Canvas API constants.
+enum {
+ Matrix = 0x01,
+ Clip = 0x02,
+ HasAlphaLayer = 0x04,
+ ClipToLayer = 0x10,
+
+ // Helper constant
+ MatrixClip = Matrix | Clip,
+};
+typedef uint32_t Flags;
+
+} // namespace android::SaveFlags
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index cd908354aea5..6030c36add7a 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -505,13 +505,11 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint)
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
- Bitmap& bitmap = getBitmapUpdateIfDirty();
- SkBitmap skiaBitmap;
- bitmap.getSkBitmap(&skiaBitmap);
+ sk_sp<SkImage> cachedBitmap = getBitmapUpdateIfDirty().makeImage();
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
- canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
+ canvas->drawImageRect(cachedBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
&paint, SkCanvas::kFast_SrcRectConstraint);
}
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 68541b4b31f0..671c66f11e32 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -26,6 +26,35 @@
namespace android::uirenderer {
+namespace {
+class ScopedCurrentFunctor {
+public:
+ ScopedCurrentFunctor(WebViewFunctor* functor) {
+ ALOG_ASSERT(!sCurrentFunctor);
+ ALOG_ASSERT(functor);
+ sCurrentFunctor = functor;
+ }
+ ~ScopedCurrentFunctor() {
+ ALOG_ASSERT(sCurrentFunctor);
+ sCurrentFunctor = nullptr;
+ }
+
+ static ASurfaceControl* getSurfaceControl() {
+ ALOG_ASSERT(sCurrentFunctor);
+ return sCurrentFunctor->getSurfaceControl();
+ }
+ static void mergeTransaction(ASurfaceTransaction* transaction) {
+ ALOG_ASSERT(sCurrentFunctor);
+ sCurrentFunctor->mergeTransaction(transaction);
+ }
+
+private:
+ static WebViewFunctor* sCurrentFunctor;
+};
+
+WebViewFunctor* ScopedCurrentFunctor::sCurrentFunctor = nullptr;
+} // namespace
+
RenderMode WebViewFunctor_queryPlatformRenderMode() {
auto pipelineType = Properties::getRenderPipelineType();
switch (pipelineType) {
@@ -83,7 +112,15 @@ void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {
if (!mHasContext) {
mHasContext = true;
}
- mCallbacks.gles.draw(mFunctor, mData, drawInfo);
+ ScopedCurrentFunctor currentFunctor(this);
+
+ WebViewOverlayData overlayParams = {
+ // TODO:
+ .overlaysMode = OverlaysMode::Disabled,
+ .getSurfaceControl = currentFunctor.getSurfaceControl,
+ .mergeTransaction = currentFunctor.mergeTransaction,
+ };
+ mCallbacks.gles.draw(mFunctor, mData, drawInfo, overlayParams);
}
void WebViewFunctor::initVk(const VkFunctorInitParams& params) {
@@ -98,7 +135,15 @@ void WebViewFunctor::initVk(const VkFunctorInitParams& params) {
void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {
ATRACE_NAME("WebViewFunctor::drawVk");
- mCallbacks.vk.draw(mFunctor, mData, params);
+ ScopedCurrentFunctor currentFunctor(this);
+
+ WebViewOverlayData overlayParams = {
+ // TODO
+ .overlaysMode = OverlaysMode::Disabled,
+ .getSurfaceControl = currentFunctor.getSurfaceControl,
+ .mergeTransaction = currentFunctor.mergeTransaction,
+ };
+ mCallbacks.vk.draw(mFunctor, mData, params, overlayParams);
}
void WebViewFunctor::postDrawVk() {
@@ -118,6 +163,20 @@ void WebViewFunctor::destroyContext() {
}
}
+void WebViewFunctor::removeOverlays() {
+ ScopedCurrentFunctor currentFunctor(this);
+ mCallbacks.removeOverlays(mFunctor, mData, currentFunctor.mergeTransaction);
+}
+
+ASurfaceControl* WebViewFunctor::getSurfaceControl() {
+ // TODO
+ return nullptr;
+}
+
+void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {
+ // TODO
+}
+
WebViewFunctorManager& WebViewFunctorManager::instance() {
static WebViewFunctorManager sInstance;
return sInstance;
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 675b738c6406..737d60525aa9 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -56,6 +56,8 @@ public:
void postDrawVk() { mReference.postDrawVk(); }
+ void removeOverlays() { mReference.removeOverlays(); }
+
private:
friend class WebViewFunctor;
@@ -71,6 +73,10 @@ public:
void drawVk(const VkFunctorDrawParams& params);
void postDrawVk();
void destroyContext();
+ void removeOverlays();
+
+ ASurfaceControl* getSurfaceControl();
+ void mergeTransaction(ASurfaceTransaction* transaction);
sp<Handle> createHandle() {
LOG_ALWAYS_FATAL_IF(mCreatedHandle);
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd786bf..0fad2d58cc8a 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -69,6 +69,7 @@ extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_fonts_Font(JNIEnv* env);
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
+extern int register_android_graphics_fonts_NativeFont(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
@@ -135,6 +136,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_drawable_VectorDrawable),
REG_JNI(register_android_graphics_fonts_Font),
REG_JNI(register_android_graphics_fonts_FontFamily),
+ REG_JNI(register_android_graphics_fonts_NativeFont),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
REG_JNI(register_android_graphics_pdf_PdfRenderer),
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
new file mode 100644
index 000000000000..8f261c83b8d3
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#include "CanvasFrontend.h"
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+
+namespace android::uirenderer {
+
+CanvasStateHelper::CanvasStateHelper(int width, int height) {
+ resetState(width, height);
+}
+
+void CanvasStateHelper::resetState(int width, int height) {
+ mInitialBounds = SkIRect::MakeWH(width, height);
+ mSaveStack.clear();
+ mClipStack.clear();
+ mTransformStack.clear();
+ mSaveStack.emplace_back();
+ mClipStack.emplace_back().setRect(mInitialBounds);
+ mTransformStack.emplace_back();
+ mCurrentClipIndex = 0;
+ mCurrentTransformIndex = 0;
+}
+
+bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
+ mSaveStack.push_back(saveEntry);
+ if (saveEntry.matrix) {
+ // We need to push before accessing transform() to ensure the reference doesn't move
+ // across vector resizes
+ mTransformStack.emplace_back() = transform();
+ mCurrentTransformIndex += 1;
+ }
+ if (saveEntry.clip) {
+ // We need to push before accessing clip() to ensure the reference doesn't move
+ // across vector resizes
+ mClipStack.emplace_back() = clip();
+ mCurrentClipIndex += 1;
+ return true;
+ }
+ return false;
+}
+
+// Assert that the cast from SkClipOp to SkRegion::Op is valid
+static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
+static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
+static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
+static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);
+
+void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
+ clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
+}
+
+void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
+ clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
+}
+
+bool CanvasStateHelper::internalRestore() {
+ // Prevent underflows
+ if (saveCount() <= 1) {
+ return false;
+ }
+
+ SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
+ mSaveStack.pop_back();
+ bool needsRestorePropagation = entry.layer;
+ if (entry.matrix) {
+ mTransformStack.pop_back();
+ mCurrentTransformIndex -= 1;
+ }
+ if (entry.clip) {
+ // We need to push before accessing clip() to ensure the reference doesn't move
+ // across vector resizes
+ mClipStack.pop_back();
+ mCurrentClipIndex -= 1;
+ needsRestorePropagation = true;
+ }
+ return needsRestorePropagation;
+}
+
+SkRect CanvasStateHelper::getClipBounds() const {
+ SkIRect ibounds = clip().getBounds();
+
+ if (ibounds.isEmpty()) {
+ return SkRect::MakeEmpty();
+ }
+
+ SkMatrix inverse;
+ // if we can't invert the CTM, we can't return local clip bounds
+ if (!transform().invert(&inverse)) {
+ return SkRect::MakeEmpty();
+ }
+
+ SkRect ret = SkRect::MakeEmpty();
+ inverse.mapRect(&ret, SkRect::Make(ibounds));
+ return ret;
+}
+
+bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
+ // TODO: Implement
+ return false;
+}
+
+bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
+ // TODO: Implement
+ return false;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
new file mode 100644
index 000000000000..d749d2f2596b
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// TODO: Can we get the dependencies scoped down more?
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+#include <SaveFlags.h>
+
+#include <SkRasterClip.h>
+#include <ui/FatVector.h>
+
+#include <optional>
+
+namespace android::uirenderer {
+
+// Exists to avoid forcing all this common logic into the templated class
+class CanvasStateHelper {
+protected:
+ CanvasStateHelper(int width, int height);
+ ~CanvasStateHelper() = default;
+
+ struct SaveEntry {
+ bool clip : 1 = false;
+ bool matrix : 1 = false;
+ bool layer : 1 = false;
+ };
+
+ constexpr SaveEntry saveEntryForLayer() {
+ return {
+ .clip = true,
+ .matrix = true,
+ .layer = true,
+ };
+ }
+
+ constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) {
+ return SaveEntry {
+ .clip = static_cast<bool>(flags & SaveFlags::Clip),
+ .matrix = static_cast<bool>(flags & SaveFlags::Matrix),
+ .layer = false
+ };
+ }
+
+ bool internalSave(SaveEntry saveEntry);
+
+ void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) {
+ internalSave({
+ .clip = true,
+ .matrix = true,
+ .layer = true
+ });
+ internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect);
+ }
+
+ bool internalRestore();
+
+ void internalClipRect(const SkRect& rect, SkClipOp op);
+ void internalClipPath(const SkPath& path, SkClipOp op);
+
+ SkIRect mInitialBounds;
+ FatVector<SaveEntry, 6> mSaveStack;
+ FatVector<SkMatrix, 6> mTransformStack;
+ FatVector<SkConservativeClip, 6> mClipStack;
+
+ size_t mCurrentTransformIndex;
+ size_t mCurrentClipIndex;
+
+ const SkConservativeClip& clip() const {
+ return mClipStack[mCurrentClipIndex];
+ }
+
+ SkConservativeClip& clip() {
+ return mClipStack[mCurrentClipIndex];
+ }
+
+ void resetState(int width, int height);
+
+public:
+ int saveCount() const { return mSaveStack.size(); }
+
+ SkRect getClipBounds() const;
+ bool quickRejectRect(float left, float top, float right, float bottom) const;
+ bool quickRejectPath(const SkPath& path) const;
+
+ const SkMatrix& transform() const {
+ return mTransformStack[mCurrentTransformIndex];
+ }
+
+ SkMatrix& transform() {
+ return mTransformStack[mCurrentTransformIndex];
+ }
+
+ // For compat with existing HWUI Canvas interface
+ void getMatrix(SkMatrix* outMatrix) const {
+ *outMatrix = transform();
+ }
+
+ void setMatrix(const SkMatrix& matrix) {
+ transform() = matrix;
+ }
+
+ void concat(const SkMatrix& matrix) {
+ transform().preConcat(matrix);
+ }
+
+ void rotate(float degrees) {
+ SkMatrix m;
+ m.setRotate(degrees);
+ concat(m);
+ }
+
+ void scale(float sx, float sy) {
+ SkMatrix m;
+ m.setScale(sx, sy);
+ concat(m);
+ }
+
+ void skew(float sx, float sy) {
+ SkMatrix m;
+ m.setSkew(sx, sy);
+ concat(m);
+ }
+
+ void translate(float dx, float dy) {
+ transform().preTranslate(dx, dy);
+ }
+};
+
+// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream
+template <typename CanvasOpReceiver>
+class CanvasFrontend final : public CanvasStateHelper {
+public:
+ template<class... Args>
+ CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
+ mReceiver(std::forward<Args>(args)...) { }
+ ~CanvasFrontend() = default;
+
+ void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
+ if (internalSave(flagsToSaveEntry(flags))) {
+ submit<CanvasOpType::Save>({});
+ }
+ }
+
+ void restore() {
+ if (internalRestore()) {
+ submit<CanvasOpType::Restore>({});
+ }
+ }
+
+ template <CanvasOpType T>
+ void draw(CanvasOp<T>&& op) {
+ // The front-end requires going through certain front-doors, which these aren't.
+ static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead");
+ static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead");
+
+ if constexpr (T == CanvasOpType::SaveLayer) {
+ internalSaveLayer(op.saveLayerRec);
+ }
+ if constexpr (T == CanvasOpType::SaveBehind) {
+ // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save
+ // But we do want to flag it as a layer, such that restore is Definitely Required
+ internalSave(saveEntryForLayer());
+ }
+ if constexpr (T == CanvasOpType::ClipRect) {
+ internalClipRect(op.rect, op.op);
+ }
+ if constexpr (T == CanvasOpType::ClipPath) {
+ internalClipPath(op.path, op.op);
+ }
+
+ submit(std::move(op));
+ }
+
+ const CanvasOpReceiver& receiver() const { return *mReceiver; }
+
+ CanvasOpReceiver finish() {
+ auto ret = std::move(mReceiver.value());
+ mReceiver.reset();
+ return std::move(ret);
+ }
+
+ template<class... Args>
+ void reset(int newWidth, int newHeight, Args&&... args) {
+ resetState(newWidth, newHeight);
+ mReceiver.emplace(std::forward<Args>(args)...);
+ }
+
+private:
+ std::optional<CanvasOpReceiver> mReceiver;
+
+ template <CanvasOpType T>
+ void submit(CanvasOp<T>&& op) {
+ mReceiver->push_container(CanvasOpContainer(std::move(op), transform()));
+ }
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpRasterizer.cpp b/libs/hwui/canvas/CanvasOpRasterizer.cpp
index 25129f641c00..0093c38cf8a8 100644
--- a/libs/hwui/canvas/CanvasOpRasterizer.cpp
+++ b/libs/hwui/canvas/CanvasOpRasterizer.cpp
@@ -33,7 +33,11 @@ void rasterizeCanvasBuffer(const CanvasOpBuffer& source, SkCanvas* destination)
SkMatrix& currentGlobalTransform = globalMatrixStack.emplace_back(SkMatrix::I());
source.for_each([&]<CanvasOpType T>(const CanvasOpContainer<T> * op) {
- if constexpr (T == CanvasOpType::BeginZ || T == CanvasOpType::EndZ) {
+ if constexpr (
+ T == CanvasOpType::BeginZ ||
+ T == CanvasOpType::EndZ ||
+ T == CanvasOpType::DrawLayer
+ ) {
// Do beginZ or endZ
LOG_ALWAYS_FATAL("TODO");
return;
diff --git a/libs/hwui/canvas/CanvasOpRecorder.cpp b/libs/hwui/canvas/CanvasOpRecorder.cpp
new file mode 100644
index 000000000000..bb968ee84670
--- /dev/null
+++ b/libs/hwui/canvas/CanvasOpRecorder.cpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#include "CanvasOpRecorder.h"
+
+#include "CanvasOpBuffer.h"
+#include "CanvasOps.h"
+
+namespace android::uirenderer {} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpRecorder.h b/libs/hwui/canvas/CanvasOpRecorder.h
new file mode 100644
index 000000000000..7d95bc4785ea
--- /dev/null
+++ b/libs/hwui/canvas/CanvasOpRecorder.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "hwui/Canvas.h"
+#include "CanvasOpBuffer.h"
+
+#include <vector>
+
+namespace android::uirenderer {
+
+// Interop with existing HWUI Canvas
+class CanvasOpRecorder final : /* todo: public Canvas */ {
+public:
+ // Transform ops
+private:
+ struct SaveEntry {
+
+ };
+
+ std::vector<SaveEntry> mSaveStack;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index f9df2f7aa5ba..f0aa7774a6cc 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -47,14 +47,17 @@ enum class CanvasOpType : int8_t {
DrawArc,
DrawPaint,
DrawPoint,
+ DrawPoints,
DrawPath,
DrawLine,
+ DrawLines,
DrawVertices,
DrawImage,
DrawImageRect,
// DrawImageLattice also used to draw 9 patches
DrawImageLattice,
DrawPicture,
+ DrawLayer,
// TODO: Rest
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 8c7113d5d075..62c26c7b6f6a 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -26,8 +26,10 @@
#include <hwui/Bitmap.h>
#include <log/log.h>
#include "CanvasProperty.h"
+#include "Points.h"
#include "CanvasOpTypes.h"
+#include "Layer.h"
#include <experimental/type_traits>
#include <utility>
@@ -165,6 +167,22 @@ struct CanvasOp<CanvasOpType::DrawPoint> {
};
template <>
+struct CanvasOp<CanvasOpType::DrawPoints> {
+ size_t count;
+ SkPaint paint;
+ sk_sp<Points> points;
+ void draw(SkCanvas* canvas) const {
+ canvas->drawPoints(
+ SkCanvas::kPoints_PointMode,
+ count,
+ points->data(),
+ paint
+ );
+ }
+ ASSERT_DRAWABLE()
+};
+
+template <>
struct CanvasOp<CanvasOpType::DrawRect> {
SkRect rect;
SkPaint paint;
@@ -263,6 +281,22 @@ struct CanvasOp<CanvasOpType::DrawLine> {
};
template<>
+struct CanvasOp<CanvasOpType::DrawLines> {
+ size_t count;
+ SkPaint paint;
+ sk_sp<Points> points;
+ void draw(SkCanvas* canvas) const {
+ canvas->drawPoints(
+ SkCanvas::kLines_PointMode,
+ count,
+ points->data(),
+ paint
+ );
+ }
+ ASSERT_DRAWABLE()
+};
+
+template<>
struct CanvasOp<CanvasOpType::DrawVertices> {
sk_sp<SkVertices> vertices;
SkBlendMode mode;
@@ -276,7 +310,7 @@ struct CanvasOp<CanvasOpType::DrawVertices> {
template<>
struct CanvasOp<CanvasOpType::DrawImage> {
- CanvasOp<CanvasOpType::DrawImageRect>(
+ CanvasOp(
const sk_sp<Bitmap>& bitmap,
float left,
float top,
@@ -302,7 +336,7 @@ struct CanvasOp<CanvasOpType::DrawImage> {
template<>
struct CanvasOp<CanvasOpType::DrawImageRect> {
- CanvasOp<CanvasOpType::DrawImageRect>(
+ CanvasOp(
const sk_sp<Bitmap>& bitmap,
SkRect src,
SkRect dst,
@@ -333,7 +367,7 @@ struct CanvasOp<CanvasOpType::DrawImageRect> {
template<>
struct CanvasOp<CanvasOpType::DrawImageLattice> {
- CanvasOp<CanvasOpType::DrawImageLattice>(
+ CanvasOp(
const sk_sp<Bitmap>& bitmap,
SkRect dst,
SkCanvas::Lattice lattice,
@@ -364,6 +398,11 @@ struct CanvasOp<CanvasOpType::DrawPicture> {
}
};
+template<>
+struct CanvasOp<CanvasOpType::DrawLayer> {
+ sp<Layer> layer;
+};
+
// cleanup our macros
#undef ASSERT_DRAWABLE
diff --git a/libs/hwui/canvas/OpBuffer.h b/libs/hwui/canvas/OpBuffer.h
index 98e385f37a6e..6dc29d9a4bfe 100644
--- a/libs/hwui/canvas/OpBuffer.h
+++ b/libs/hwui/canvas/OpBuffer.h
@@ -60,9 +60,8 @@ class OpBuffer {
return (size + (Alignment - 1)) & -Alignment;
}
- static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
-
public:
+ static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
using ItemHeader = OpBufferItemHeader<ItemTypes>;
OpBuffer() = default;
diff --git a/libs/hwui/canvas/Points.h b/libs/hwui/canvas/Points.h
new file mode 100644
index 000000000000..05e6a7dd5884
--- /dev/null
+++ b/libs/hwui/canvas/Points.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+#include <ui/FatVector.h>
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+
+/**
+ * Collection of points that are ref counted and to be used with
+ * various drawing calls that consume SkPoint as inputs like
+ * drawLines/drawPoints
+ */
+class Points: public SkNVRefCnt<SkPoint> {
+public:
+ Points(int size){
+ skPoints.resize(size);
+ }
+
+ Points(std::initializer_list<SkPoint> init): skPoints(init) { }
+
+ SkPoint& operator[](int index) {
+ return skPoints[index];
+ }
+
+ const SkPoint* data() const {
+ return skPoints.data();
+ }
+
+ size_t size() const {
+ return skPoints.size();
+ }
+private:
+ // Initialize the size to contain 2 SkPoints on the stack for optimized
+ // drawLine calls that require 2 SkPoints for start/end points of the line
+ android::FatVector<SkPoint, 2> skPoints;
+};
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index f94bae2746d9..11fa3223a9c8 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -18,8 +18,10 @@
#include <cutils/compiler.h>
#include <utils/Functor.h>
+#include <SaveFlags.h>
#include <androidfw/ResourceTypes.h>
+#include "DisplayList.h"
#include "Properties.h"
#include "utils/Macros.h"
@@ -46,34 +48,6 @@ class CanvasPropertyPaint;
class CanvasPropertyPrimitive;
class DeferredLayerUpdater;
class RenderNode;
-
-namespace skiapipeline {
-class SkiaDisplayList;
-}
-
-/**
- * Data structure that holds the list of commands used in display list stream
- */
-using DisplayList = skiapipeline::SkiaDisplayList;
-}
-
-namespace SaveFlags {
-
-// These must match the corresponding Canvas API constants.
-enum {
- Matrix = 0x01,
- Clip = 0x02,
- HasAlphaLayer = 0x04,
- ClipToLayer = 0x10,
-
- // Helper constant
- MatrixClip = Matrix | Clip,
-};
-typedef uint32_t Flags;
-
-} // namespace SaveFlags
-
-namespace uirenderer {
namespace VectorDrawable {
class Tree;
}
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f0c77930cbe3..f612bce748ff 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -187,38 +187,6 @@ static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong
}
// Critical Native
-static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
-
- uint64_t result = font->style().weight();
- result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
- result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
- result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
- return result;
-}
-
-// Critical Native
-static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
- const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
- uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
- return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
-}
-
-// FastNative
-static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
- const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
- const std::string& filePath = minikinSkia->getFilePath();
- if (filePath.empty()) {
- return nullptr;
- }
- return env->NewStringUTF(filePath.c_str());
-}
-
-// Critical Native
static jlong Font_getNativeFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
return reinterpret_cast<jlong>(font->font.get());
@@ -276,9 +244,6 @@ static const JNINativeMethod gFontBuilderMethods[] = {
static const JNINativeMethod gFontMethods[] = {
{ "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds },
{ "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
- { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
- { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
- { "nGetFontPath", "(J)Ljava/lang/String;", (void*) Font_getFontPath },
{ "nGetNativeFontPtr", "(J)J", (void*) Font_getNativeFontPtr },
{ "nGetFontBufferAddress", "(J)J", (void*) Font_GetBufferAddress },
};
diff --git a/libs/hwui/jni/fonts/NativeFont.cpp b/libs/hwui/jni/fonts/NativeFont.cpp
new file mode 100644
index 000000000000..c5c5d464ccac
--- /dev/null
+++ b/libs/hwui/jni/fonts/NativeFont.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Minikin"
+
+#include "Font.h"
+#include "SkData.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+#include "SkTypeface.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/ScopedUtfChars.h>
+#include "Utils.h"
+#include "FontUtils.h"
+
+#include <hwui/MinikinSkia.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/FontFamily.h>
+#include <minikin/LocaleList.h>
+#include <ui/FatVector.h>
+
+#include <memory>
+
+namespace android {
+
+// Critical Native
+static jint NativeFont_getFamilyCount(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle) {
+ Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+ return tf->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong NativeFont_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong typefaceHandle, jint index) {
+ Typeface* tf = reinterpret_cast<Typeface*>(typefaceHandle);
+ return reinterpret_cast<jlong>(tf->fFontCollection->getFamilies()[index].get());
+
+}
+
+// Fast Native
+static jstring NativeFont_getLocaleList(JNIEnv* env, jobject, jlong familyHandle) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ uint32_t localeListId = family->localeListId();
+ return env->NewStringUTF(minikin::getLocaleString(localeListId).c_str());
+}
+
+// Critical Native
+static jint NativeFont_getFontCount(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ return family->getNumFonts();
+}
+
+// Critical Native
+static jlong NativeFont_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyHandle, jint index) {
+ minikin::FontFamily* family = reinterpret_cast<minikin::FontFamily*>(familyHandle);
+ return reinterpret_cast<jlong>(family->getFont(index));
+}
+
+// Critical Native
+static jlong NativeFont_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+
+ uint64_t result = font->style().weight();
+ result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
+ result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
+ result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
+ return result;
+}
+
+// Critical Native
+static jlong NativeFont_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
+ uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
+ return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
+}
+
+// FastNative
+static jstring NativeFont_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const std::string& filePath = minikinSkia->getFilePath();
+ if (filePath.empty()) {
+ return nullptr;
+ }
+ return env->NewStringUTF(filePath.c_str());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const JNINativeMethod gNativeFontMethods[] = {
+ { "nGetFamilyCount", "(J)I", (void*) NativeFont_getFamilyCount },
+ { "nGetFamily", "(JI)J", (void*) NativeFont_getFamily },
+ { "nGetLocaleList", "(J)Ljava/lang/String;", (void*) NativeFont_getLocaleList },
+ { "nGetFontCount", "(J)I", (void*) NativeFont_getFontCount },
+ { "nGetFont", "(JI)J", (void*) NativeFont_getFont },
+ { "nGetFontInfo", "(J)J", (void*) NativeFont_getFontInfo },
+ { "nGetAxisInfo", "(JI)J", (void*) NativeFont_getAxisInfo },
+ { "nGetFontPath", "(J)Ljava/lang/String;", (void*) NativeFont_getFontPath },
+};
+
+int register_android_graphics_fonts_NativeFont(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFont", gNativeFontMethods,
+ NELEM(gNativeFontMethods));
+}
+
+} // namespace android
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index e65921ac8e0a..427bafa1bd83 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -129,8 +129,8 @@ static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPt
// PDF's coordinate system origin is left-bottom while in graphics it
// is the top-left. So, translate the PDF coordinates to ours.
- SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
- SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
+ SkMatrix reflectOnX = SkMatrix::Scale(1, -1);
+ SkMatrix moveUp = SkMatrix::Translate(0, FPDF_GetPageHeight(page));
SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX);
// Apply the transformation what was created in our coordinates.
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 158c3493a90c..c63f5d349311 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -47,7 +47,7 @@ void SkiaDisplayList::syncContents(const WebViewSyncData& data) {
}
}
-bool SkiaDisplayList::reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) {
+bool SkiaDisplayList::reuseDisplayList(RenderNode* node) {
reset();
node->attachAvailableList(this);
return true;
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index cdd00db9afdc..f2f19ba2975e 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -98,7 +98,7 @@ public:
*
* @return true if the displayList will be reused and therefore should not be deleted
*/
- bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context);
+ bool reuseDisplayList(RenderNode* node);
/**
* ONLY to be called by RenderNode::syncDisplayList so that we can notify any
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index 96da947ace08..22ae59e5137b 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -17,6 +17,15 @@
#ifndef FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H
+#ifdef __ANDROID__ // Layoutlib does not support surface control
+#include <android/surface_control.h>
+#else
+// To avoid ifdefs around overlay implementation all over the place we typedef these to void *. They
+// won't be used.
+typedef void* ASurfaceControl;
+typedef void* ASurfaceTransaction;
+#endif
+
#include <cutils/compiler.h>
#include <private/hwui/DrawGlInfo.h>
#include <private/hwui/DrawVkInfo.h>
@@ -28,6 +37,14 @@ enum class RenderMode {
Vulkan,
};
+enum class OverlaysMode {
+ // Indicated that webview should not promote anything to overlays this draw
+ // and remove all visible overlays.
+ Disabled,
+ // Indicates that webview can use overlays.
+ Enabled
+};
+
// Static for the lifetime of the process
ANDROID_API RenderMode WebViewFunctor_queryPlatformRenderMode();
@@ -35,6 +52,23 @@ struct WebViewSyncData {
bool applyForceDark;
};
+struct WebViewOverlayData {
+ // Desired overlay mode for this draw.
+ OverlaysMode overlaysMode;
+
+ // Returns parent ASurfaceControl for WebView overlays. It will be have same
+ // geometry as the surface we draw into and positioned below it (underlay).
+ // This does not pass ownership to webview, but guaranteed to be alive until
+ // transaction from next removeOverlays call or functor destruction will be
+ // finished.
+ ASurfaceControl* (*getSurfaceControl)();
+
+ // Merges WebView transaction to be applied synchronously with current draw.
+ // This doesn't pass ownership of the transaction, changes will be copied and
+ // webview can free transaction right after the call.
+ void (*mergeTransaction)(ASurfaceTransaction*);
+};
+
struct WebViewFunctorCallbacks {
// kModeSync, called on RenderThread
void (*onSync)(int functor, void* data, const WebViewSyncData& syncData);
@@ -48,16 +82,23 @@ struct WebViewFunctorCallbacks {
// this functor had ever been drawn.
void (*onDestroyed)(int functor, void* data);
+ // Called on render thread to force webview hide all overlays and stop updating them.
+ // Should happen during hwui draw (e.g can be called instead of draw if webview
+ // isn't visible and won't receive draw) and support MergeTransaction call.
+ void (*removeOverlays)(int functor, void* data, void (*mergeTransaction)(ASurfaceTransaction*));
+
union {
struct {
// Called on RenderThread. initialize is guaranteed to happen before this call
- void (*draw)(int functor, void* data, const DrawGlInfo& params);
+ void (*draw)(int functor, void* data, const DrawGlInfo& params,
+ const WebViewOverlayData& overlayParams);
} gles;
struct {
// Called either the first time the functor is used or the first time it's used after
// a call to onContextDestroyed.
void (*initialize)(int functor, void* data, const VkFunctorInitParams& params);
- void (*draw)(int functor, void* data, const VkFunctorDrawParams& params);
+ void (*draw)(int functor, void* data, const VkFunctorDrawParams& params,
+ const WebViewOverlayData& overlayParams);
void (*postDraw)(int functor, void*);
} vk;
};
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 36c5a8c1b3de..c1d8b761514b 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -323,7 +323,8 @@ public:
};
switch (mode) {
case RenderMode::OpenGL_ES:
- callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params) {
+ callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params,
+ const WebViewOverlayData& overlay_params) {
expectOnRenderThread("draw");
sMockFunctorCounts[functor].glesDraw++;
};
diff --git a/libs/hwui/tests/microbench/CanvasOpBench.cpp b/libs/hwui/tests/microbench/CanvasOpBench.cpp
new file mode 100644
index 000000000000..ef5749e6b79b
--- /dev/null
+++ b/libs/hwui/tests/microbench/CanvasOpBench.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <benchmark/benchmark.h>
+
+#include "DisplayList.h"
+#include "hwui/Paint.h"
+#include "canvas/CanvasOpBuffer.h"
+#include "canvas/CanvasFrontend.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+void BM_CanvasOpBuffer_alloc(benchmark::State& benchState) {
+ while (benchState.KeepRunning()) {
+ auto displayList = new CanvasOpBuffer();
+ benchmark::DoNotOptimize(displayList);
+ delete displayList;
+ }
+}
+BENCHMARK(BM_CanvasOpBuffer_alloc);
+
+void BM_CanvasOpBuffer_record_saverestore(benchmark::State& benchState) {
+ CanvasFrontend<CanvasOpBuffer> canvas(100, 100);
+ while (benchState.KeepRunning()) {
+ canvas.reset(100, 100);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.save(SaveFlags::MatrixClip);
+ benchmark::DoNotOptimize(&canvas);
+ canvas.restore();
+ canvas.restore();
+ canvas.finish();
+ }
+}
+BENCHMARK(BM_CanvasOpBuffer_record_saverestore);
+
+void BM_CanvasOpBuffer_record_saverestoreWithReuse(benchmark::State& benchState) {
+ CanvasFrontend<CanvasOpBuffer> canvas(100, 100);
+
+ while (benchState.KeepRunning()) {
+ canvas.reset(100, 100);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.save(SaveFlags::MatrixClip);
+ benchmark::DoNotOptimize(&canvas);
+ canvas.restore();
+ canvas.restore();
+ }
+}
+BENCHMARK(BM_CanvasOpBuffer_record_saverestoreWithReuse);
+
+void BM_CanvasOpBuffer_record_simpleBitmapView(benchmark::State& benchState) {
+ CanvasFrontend<CanvasOpBuffer> canvas(100, 100);
+
+ Paint rectPaint;
+ sk_sp<Bitmap> iconBitmap(TestUtils::createBitmap(80, 80));
+
+ while (benchState.KeepRunning()) {
+ canvas.reset(100, 100);
+ {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.draw(CanvasOp<CanvasOpType::DrawRect> {
+ .rect = SkRect::MakeWH(100, 100),
+ .paint = rectPaint,
+ });
+ canvas.restore();
+ }
+ {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(10, 10);
+ canvas.draw(CanvasOp<CanvasOpType::DrawImage> {
+ iconBitmap,
+ 0,
+ 0,
+ SkPaint{}
+ });
+ canvas.restore();
+ }
+ benchmark::DoNotOptimize(&canvas);
+ canvas.finish();
+ }
+}
+BENCHMARK(BM_CanvasOpBuffer_record_simpleBitmapView);
diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp
index 206dcd58d785..011939a6e4b2 100644
--- a/libs/hwui/tests/microbench/RenderNodeBench.cpp
+++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp
@@ -30,3 +30,29 @@ void BM_RenderNode_create(benchmark::State& state) {
}
}
BENCHMARK(BM_RenderNode_create);
+
+void BM_RenderNode_recordSimple(benchmark::State& state) {
+ sp<RenderNode> node = new RenderNode();
+ std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(100, 100));
+ delete canvas->finishRecording();
+
+ while (state.KeepRunning()) {
+ canvas->resetRecording(100, 100, node.get());
+ canvas->drawColor(0x00000000, SkBlendMode::kSrcOver);
+ node->setStagingDisplayList(canvas->finishRecording());
+ }
+}
+BENCHMARK(BM_RenderNode_recordSimple);
+
+void BM_RenderNode_recordSimpleWithReuse(benchmark::State& state) {
+ sp<RenderNode> node = new RenderNode();
+ std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(100, 100));
+ delete canvas->finishRecording();
+
+ while (state.KeepRunning()) {
+ canvas->resetRecording(100, 100, node.get());
+ canvas->drawColor(0x00000000, SkBlendMode::kSrcOver);
+ canvas->finishRecording()->reuseDisplayList(node.get());
+ }
+}
+BENCHMARK(BM_RenderNode_recordSimpleWithReuse); \ No newline at end of file
diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
new file mode 100644
index 000000000000..05b11795d90d
--- /dev/null
+++ b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <canvas/CanvasFrontend.h>
+#include <canvas/CanvasOpBuffer.h>
+#include <canvas/CanvasOps.h>
+#include <canvas/CanvasOpRasterizer.h>
+
+#include <tests/common/CallCountingCanvas.h>
+
+#include "SkPictureRecorder.h"
+#include "SkColor.h"
+#include "SkLatticeIter.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::test;
+
+class CanvasOpCountingReceiver {
+public:
+ template <CanvasOpType T>
+ void push_container(CanvasOpContainer<T>&& op) {
+ mOpCounts[static_cast<size_t>(T)] += 1;
+ }
+
+ int operator[](CanvasOpType op) const {
+ return mOpCounts[static_cast<size_t>(op)];
+ }
+
+private:
+ std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
+TEST(CanvasFrontend, saveCount) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::MatrixClip);
+ EXPECT_EQ(2, skiaCanvas.getSaveCount());
+ EXPECT_EQ(2, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ EXPECT_EQ(1, receiver[CanvasOpType::Save]);
+ EXPECT_EQ(1, receiver[CanvasOpType::Restore]);
+}
+
+TEST(CanvasFrontend, transform) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+
+ skiaCanvas.translate(10, 10);
+ opCanvas.translate(10, 10);
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.scale(2.0f, 1.125f);
+ opCanvas.scale(2.0f, 1.125f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.rotate(90.f);
+ opCanvas.rotate(90.f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+ {
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::Matrix);
+ skiaCanvas.skew(5.0f, 2.25f);
+ opCanvas.skew(5.0f, 2.25f);
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ }
+
+ EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+}
+
+TEST(CanvasFrontend, drawOpTransform) {
+ CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ auto makeDrawRect = [] {
+ return CanvasOp<CanvasOpType::DrawRect>{
+ .rect = SkRect::MakeWH(50, 50),
+ .paint = SkPaint(SkColors::kBlack),
+ };
+ };
+
+ opCanvas.draw(makeDrawRect());
+
+ opCanvas.translate(10, 10);
+ opCanvas.draw(makeDrawRect());
+
+ opCanvas.save();
+ opCanvas.scale(2.0f, 4.0f);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.restore();
+
+ opCanvas.save();
+ opCanvas.translate(20, 15);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.save();
+ opCanvas.rotate(90.f);
+ opCanvas.draw(makeDrawRect());
+ opCanvas.restore();
+ opCanvas.restore();
+
+ // Validate the results
+ std::vector<SkMatrix> transforms;
+ transforms.reserve(5);
+ receiver.for_each([&](auto op) {
+ // Filter for the DrawRect calls; ignore the save & restores
+ // (TODO: Add a filtered for_each variant to OpBuffer?)
+ if (op->type() == CanvasOpType::DrawRect) {
+ transforms.push_back(op->transform());
+ }
+ });
+
+ EXPECT_EQ(transforms.size(), 5);
+
+ {
+ // First result should be identity
+ const auto& result = transforms[0];
+ EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType());
+ EXPECT_EQ(SkMatrix::I(), result);
+ }
+
+ {
+ // Should be translate 10, 10
+ const auto& result = transforms[1];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(10, 10);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + scale 2, 4
+ const auto& result = transforms[2];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(10, 10);
+ m.preScale(2.0f, 4.0f);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + translate 20, 15
+ const auto& result = transforms[3];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+ SkMatrix m;
+ m.setTranslate(30, 25);
+ EXPECT_EQ(m, result);
+ }
+
+ {
+ // Should be translate 10, 10 + translate 20, 15 + rotate 90
+ const auto& result = transforms[4];
+ EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask,
+ result.getType());
+ SkMatrix m;
+ m.setTranslate(30, 25);
+ m.preRotate(90.f);
+ EXPECT_EQ(m, result);
+ }
+} \ No newline at end of file
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index b15c3221dd60..033a5872ead3 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -16,6 +16,7 @@
#include <gtest/gtest.h>
+#include <canvas/CanvasFrontend.h>
#include <canvas/CanvasOpBuffer.h>
#include <canvas/CanvasOps.h>
#include <canvas/CanvasOpRasterizer.h>
@@ -26,6 +27,7 @@
#include "SkColor.h"
#include "SkLatticeIter.h"
#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
using namespace android;
using namespace android::uirenderer;
@@ -78,6 +80,21 @@ struct MockOp<MockTypes::Lifecycle> {
using MockBuffer = OpBuffer<MockTypes, MockOpContainer>;
+class CanvasOpCountingReceiver {
+public:
+ template <CanvasOpType T>
+ void push_container(CanvasOpContainer<T>&& op) {
+ mOpCounts[static_cast<size_t>(T)] += 1;
+ }
+
+ int operator[](CanvasOpType op) const {
+ return mOpCounts[static_cast<size_t>(op)];
+ }
+
+private:
+ std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
template<typename T>
static int countItems(const T& t) {
int count = 0;
@@ -228,6 +245,31 @@ TEST(CanvasOp, simpleDrawPoint) {
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
+TEST(CanvasOp, simpleDrawPoints) {
+ CanvasOpBuffer buffer;
+ EXPECT_EQ(buffer.size(), 0);
+ size_t numPts = 3;
+ auto pts = sk_ref_sp(
+ new Points({
+ {32, 16},
+ {48, 48},
+ {16, 32}
+ })
+ );
+
+ buffer.push(CanvasOp<Op::DrawPoints> {
+ .count = numPts,
+ .paint = SkPaint{},
+ .points = pts
+ });
+
+ CallCountingCanvas canvas;
+ EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+ rasterizeCanvasBuffer(buffer, &canvas);
+ EXPECT_EQ(1, canvas.drawPoints);
+ EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
TEST(CanvasOp, simpleDrawLine) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
@@ -246,6 +288,30 @@ TEST(CanvasOp, simpleDrawLine) {
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
+TEST(CanvasOp, simpleDrawLines) {
+ CanvasOpBuffer buffer;
+ EXPECT_EQ(buffer.size(), 0);
+ size_t numPts = 3;
+ auto pts = sk_ref_sp(
+ new Points({
+ {32, 16},
+ {48, 48},
+ {16, 32}
+ })
+ );
+ buffer.push(CanvasOp<Op::DrawLines> {
+ .count = numPts,
+ .paint = SkPaint{},
+ .points = pts
+ });
+
+ CallCountingCanvas canvas;
+ EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+ rasterizeCanvasBuffer(buffer, &canvas);
+ EXPECT_EQ(1, canvas.drawPoints);
+ EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
TEST(CanvasOp, simpleDrawRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
@@ -614,4 +680,35 @@ TEST(CanvasOp, immediateRendering) {
rasterizer.draw(op);
EXPECT_EQ(1, canvas->drawRectCount);
EXPECT_EQ(1, canvas->sumTotalDrawCalls());
+}
+
+TEST(CanvasOp, frontendSaveCount) {
+ SkNoDrawCanvas skiaCanvas(100, 100);
+ CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+ const auto& receiver = opCanvas.receiver();
+
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.save();
+ opCanvas.save(SaveFlags::MatrixClip);
+ EXPECT_EQ(2, skiaCanvas.getSaveCount());
+ EXPECT_EQ(2, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ skiaCanvas.restore();
+ opCanvas.restore();
+ EXPECT_EQ(1, skiaCanvas.getSaveCount());
+ EXPECT_EQ(1, opCanvas.saveCount());
+
+ EXPECT_EQ(1, receiver[Op::Save]);
+ EXPECT_EQ(1, receiver[Op::Restore]);
+}
+
+TEST(CanvasOp, frontendTransform) {
+
} \ No newline at end of file
diff --git a/libs/hwui/tests/unit/CommonPoolTests.cpp b/libs/hwui/tests/unit/CommonPoolTests.cpp
index da6a2604a4b6..bffdeca4db54 100644
--- a/libs/hwui/tests/unit/CommonPoolTests.cpp
+++ b/libs/hwui/tests/unit/CommonPoolTests.cpp
@@ -54,7 +54,9 @@ TEST(DISABLED_CommonPool, threadCount) {
EXPECT_EQ(0, threads.count(gettid()));
}
-TEST(CommonPool, singleThread) {
+// Disabled since this is flaky. This isn't a necessarily useful functional test, so being
+// disabled isn't that significant. However it may be good to resurrect this somehow.
+TEST(CommonPool, DISABLED_singleThread) {
std::mutex mutex;
std::condition_variable fence;
bool isProcessing = false;
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index f4c3e13b0ea6..955a5e7d8b3a 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -39,7 +39,7 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) {
EXPECT_EQ(Matrix4::identity(), layerUpdater->backingLayer()->getTexTransform());
// push the deferred updates to the layer
- SkMatrix scaledMatrix = SkMatrix::MakeScale(0.5, 0.5);
+ SkMatrix scaledMatrix = SkMatrix::Scale(0.5, 0.5);
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index abdf9d587189..26bc65915c7a 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -206,7 +206,7 @@ TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
ASSERT_EQ(SkRect::MakeLTRB(50, 50, 350, 350), getRecorderClipBounds(recorder));
recorder.translate(300.0f, 400.0f);
- EXPECT_EQ(SkMatrix::MakeTrans(300.0f, 400.0f), getRecorderMatrix(recorder));
+ EXPECT_EQ(SkMatrix::Translate(300.0f, 400.0f), getRecorderMatrix(recorder));
recorder.restore();
ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder));
@@ -1107,27 +1107,27 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) {
EXPECT_EQ(dy, TRANSLATE_Y);
}
- virtual void didSetMatrix(const SkMatrix& matrix) override {
+ virtual void didSetM44(const SkM44& matrix) override {
mDrawCounter++;
// First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
// Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
- EXPECT_TRUE(matrix.isIdentity());
+ EXPECT_TRUE(matrix == SkM44());
EXPECT_TRUE(getTotalMatrix().isIdentity());
}
- virtual void didConcat(const SkMatrix& matrix) override {
+ virtual void didConcat44(const SkM44& matrix) override {
mDrawCounter++;
if (mFirstDidConcat) {
// First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
mFirstDidConcat = false;
- EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
+ EXPECT_EQ(SkM44::Translate(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
matrix);
- EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
+ EXPECT_EQ(SkMatrix::Translate(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
getTotalMatrix());
} else {
// Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
- EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), matrix);
- EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), getTotalMatrix());
+ EXPECT_EQ(SkM44::Translate(TRANSLATE_X, TRANSLATE_Y), matrix);
+ EXPECT_EQ(SkMatrix::Translate(TRANSLATE_X, TRANSLATE_Y), getTotalMatrix());
}
}
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index c19e1ed6ce75..4659a929a9eb 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -264,6 +264,8 @@ TEST(RenderNode, releasedCallback) {
TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) {
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
});
+ // Fence on any remaining post'd work
+ TestUtils::runOnRenderThreadUnmanaged([] (RenderThread&) {});
EXPECT_EQ(2, counts.sync);
EXPECT_EQ(1, counts.destroyed);
}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 74a565439f85..c63f008c4aed 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -84,7 +84,7 @@ TEST(SkiaDisplayList, reuseDisplayList) {
// attach a displayList for reuse
SkiaDisplayList skiaDL;
- ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get(), nullptr));
+ ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get()));
// detach the list that you just attempted to reuse
availableList = renderNode->detachAvailableList();
@@ -263,10 +263,10 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr
}
// Another way to be offscreen: a matrix from the draw call.
- for (const SkMatrix translate : { SkMatrix::MakeTrans(width, 0),
- SkMatrix::MakeTrans(0, height),
- SkMatrix::MakeTrans(-width, 0),
- SkMatrix::MakeTrans(0, -height)}) {
+ for (const SkMatrix translate : { SkMatrix::Translate(width, 0),
+ SkMatrix::Translate(0, height),
+ SkMatrix::Translate(-width, 0),
+ SkMatrix::Translate(0, -height)}) {
SkiaDisplayList skiaDL;
VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
dirtyVD.mutateProperties()->setBounds(bounds);
@@ -291,7 +291,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr
SkiaDisplayList skiaDL;
VectorDrawableRoot dirtyVD(new VectorDrawable::Group());
dirtyVD.mutateProperties()->setBounds(bounds);
- SkMatrix translate = SkMatrix::MakeTrans(50, 50);
+ SkMatrix translate = SkMatrix::Translate(50, 50);
skiaDL.appendVD(&dirtyVD, translate);
ASSERT_TRUE(dirtyVD.isDirty());
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index eec25c6bd40d..15ecf5831f3a 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -111,11 +111,11 @@ TEST(RenderNodeDrawable, renderPropTransform) {
[](RenderProperties& properties) {
properties.setLeftTopRightBottom(10, 10, 110, 110);
- SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f);
+ SkMatrix staticMatrix = SkMatrix::Scale(1.2f, 1.2f);
properties.setStaticMatrix(&staticMatrix);
// ignored, since static overrides animation
- SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15);
+ SkMatrix animationMatrix = SkMatrix::Translate(15, 15);
properties.setAnimationMatrix(&animationMatrix);
properties.setTranslationX(10);
diff --git a/libs/incident/OWNERS b/libs/incident/OWNERS
new file mode 100644
index 000000000000..f76611555dbb
--- /dev/null
+++ b/libs/incident/OWNERS
@@ -0,0 +1 @@
+include /cmds/incidentd/OWNERS
diff --git a/libs/input/OWNERS b/libs/input/OWNERS
new file mode 100644
index 000000000000..d701f23cb9b8
--- /dev/null
+++ b/libs/input/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/hardware/input/OWNERS
diff --git a/libs/storage/OWNERS b/libs/storage/OWNERS
new file mode 100644
index 000000000000..6f9dbea36b06
--- /dev/null
+++ b/libs/storage/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/os/storage/OWNERS
diff --git a/libs/usb/OWNERS b/libs/usb/OWNERS
new file mode 100644
index 000000000000..f7b2a37a297a
--- /dev/null
+++ b/libs/usb/OWNERS
@@ -0,0 +1 @@
+include /services/usb/OWNERS