summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml153
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java234
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java290
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java212
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt143
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt177
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java79
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java83
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java111
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java76
-rw-r--r--libs/hwui/hwui/Typeface.cpp9
-rw-r--r--libs/hwui/hwui/Typeface.h3
-rw-r--r--libs/hwui/jni/Typeface.cpp25
-rw-r--r--libs/hwui/renderthread/VulkanManager.h2
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp67
65 files changed, 1823 insertions, 438 deletions
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 8533a5994d33..b0dab90b6add 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,92 +16,98 @@
-->
<!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tv_pip_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center|top">
+ android:id="@+id/tv_pip_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|top">
<!-- Matches the PiP app content -->
- <View
+ <FrameLayout
android:id="@+id/tv_pip"
android:layout_width="0dp"
android:layout_height="0dp"
- android:alpha="0"
- android:background="@color/tv_pip_menu_background"
android:layout_marginTop="@dimen/pip_menu_outer_space"
android:layout_marginStart="@dimen/pip_menu_outer_space"
- android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
-
- <ScrollView
- android:id="@+id/tv_pip_menu_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none"
- android:visibility="gone"/>
-
- <HorizontalScrollView
- android:id="@+id/tv_pip_menu_horizontal_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none">
-
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:alpha="0">
+ android:layout_marginEnd="@dimen/pip_menu_outer_space">
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
+ <View
+ android:id="@+id/tv_pip_menu_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_background"
+ android:alpha="0"/>
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
+ <View
+ android:id="@+id/tv_pip_menu_dim_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_dim_layer"
+ android:alpha="0"/>
- <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+ <ScrollView
+ android:id="@+id/tv_pip_menu_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none"
+ android:visibility="gone"/>
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_move_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_move_white"
- android:text="@string/pip_move" />
+ <HorizontalScrollView
+ android:id="@+id/tv_pip_menu_horizontal_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none">
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_expand_button"
+ <LinearLayout
+ android:id="@+id/tv_pip_menu_action_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_collapse"
- android:visibility="gone"
- android:text="@string/pip_collapse" />
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- </LinearLayout>
- </HorizontalScrollView>
+ android:orientation="horizontal"
+ android:alpha="0">
+
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_fullscreen_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_fullscreen_white"
+ android:text="@string/pip_fullscreen" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white"
+ android:text="@string/pip_close" />
+
+ <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_move_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_move_white"
+ android:text="@string/pip_move" />
+
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_collapse"
+ android:visibility="gone"
+ android:text="@string/pip_collapse" />
+
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+ </LinearLayout>
+ </HorizontalScrollView>
+ </FrameLayout>
+ <!-- Frame around the content, just overlapping the corners to make them round -->
<View
android:id="@+id/tv_pip_border"
android:layout_width="0dp"
@@ -111,6 +117,7 @@
android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
+ <!-- Temporarily extending the background to show an edu text hint for opening the menu -->
<FrameLayout
android:id="@+id/tv_pip_menu_edu_text_container"
android:layout_width="match_parent"
@@ -138,6 +145,7 @@
android:textAppearance="@style/TvPipEduText"/>
</FrameLayout>
+ <!-- Frame around the PiP content + edu text hint - used to highlight open menu -->
<View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
@@ -145,7 +153,8 @@
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
- <com.android.wm.shell.common.TvWindowMenuActionButton
+ <!-- Move menu -->
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_done_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 3e71c1010278..e6933ca3fce6 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -25,6 +25,7 @@
<color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>
+ <color name="tv_pip_menu_dim_layer">#990E0E0F</color>
<color name="tv_pip_menu_background">#1E232C</color>
<color name="tv_pip_edu_text">#99D2E3FC</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
new file mode 100644
index 000000000000..cc4db933ec9f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2022 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.activityembedding;
+
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MTRANS_X;
+import static android.graphics.Matrix.MTRANS_Y;
+
+import android.annotation.CallSuper;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the ActivityEmbedding animation update in one
+ * {@link SurfaceControl.Transaction}.
+ */
+class ActivityEmbeddingAnimationAdapter {
+
+ /**
+ * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
+ */
+ private static final int LAYER_NO_OVERRIDE = -1;
+
+ final Animation mAnimation;
+ final TransitionInfo.Change mChange;
+ final SurfaceControl mLeash;
+
+ final Transformation mTransformation = new Transformation();
+ final float[] mMatrix = new float[9];
+ final float[] mVecs = new float[4];
+ final Rect mRect = new Rect();
+ private boolean mIsFirstFrame = true;
+ private int mOverrideLayer = LAYER_NO_OVERRIDE;
+
+ ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
+ @NonNull TransitionInfo.Change change) {
+ this(animation, change, change.getLeash());
+ }
+
+ /**
+ * @param leash the surface to animate, which is not necessary the same as
+ * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+ */
+ ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
+ @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) {
+ mAnimation = animation;
+ mChange = change;
+ mLeash = leash;
+ }
+
+ /**
+ * Surface layer to be set at the first frame of the animation. We will not set the layer if it
+ * is set to {@link #LAYER_NO_OVERRIDE}.
+ */
+ final void overrideLayer(int layer) {
+ mOverrideLayer = layer;
+ }
+
+ /** Called on frame update. */
+ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ if (mIsFirstFrame) {
+ t.show(mLeash);
+ if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+ t.setLayer(mLeash, mOverrideLayer);
+ }
+ mIsFirstFrame = false;
+ }
+
+ // Extract the transformation to the current time.
+ mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+ mTransformation);
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ onAnimationUpdateInner(t);
+ }
+
+ /** To be overridden by subclasses to adjust the animation surface change. */
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ final Point offset = mChange.getEndRelOffset();
+ mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ // Get current animation position.
+ final int positionX = Math.round(mMatrix[MTRANS_X]);
+ final int positionY = Math.round(mMatrix[MTRANS_Y]);
+ // The exiting surface starts at position: Change#getEndRelOffset() and moves with
+ // positionX varying. Offset our crop region by the amount we have slided so crop
+ // regions stays exactly on the original container in split.
+ final int cropOffsetX = offset.x - positionX;
+ final int cropOffsetY = offset.y - positionY;
+ final Rect cropRect = new Rect();
+ cropRect.set(mChange.getEndAbsBounds());
+ // Because window crop uses absolute position.
+ cropRect.offsetTo(0, 0);
+ cropRect.offset(cropOffsetX, cropOffsetY);
+ t.setCrop(mLeash, cropRect);
+ }
+
+ /** Called after animation finished. */
+ @CallSuper
+ void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ onAnimationUpdate(t, mAnimation.getDuration());
+ }
+
+ final long getDurationHint() {
+ return mAnimation.computeDurationHint();
+ }
+
+ /**
+ * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to
+ * animate together as one. This adapter will offset the animation leash to make the animate of
+ * two windows look like a single window.
+ */
+ static class SplitAdapter extends ActivityEmbeddingAnimationAdapter {
+ private final boolean mIsLeftHalf;
+ private final int mWholeAnimationWidth;
+
+ /**
+ * @param isLeftHalf whether this is the left half of the animation.
+ * @param wholeAnimationWidth the whole animation windows width.
+ */
+ SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
+ boolean isLeftHalf, int wholeAnimationWidth) {
+ super(animation, change);
+ mIsLeftHalf = isLeftHalf;
+ mWholeAnimationWidth = wholeAnimationWidth;
+ if (wholeAnimationWidth == 0) {
+ throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
+ }
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ final Point offset = mChange.getEndRelOffset();
+ float posX = offset.x;
+ final float posY = offset.y;
+ // This window is half of the whole animation window. Offset left/right to make it
+ // look as one with the other half.
+ mTransformation.getMatrix().getValues(mMatrix);
+ final int changeWidth = mChange.getEndAbsBounds().width();
+ final float scaleX = mMatrix[MSCALE_X];
+ final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
+ final float curOffset = changeWidth * (1 - scaleX) / 2;
+ final float offsetDiff = totalOffset - curOffset;
+ if (mIsLeftHalf) {
+ posX += offsetDiff;
+ } else {
+ posX -= offsetDiff;
+ }
+ mTransformation.getMatrix().postTranslate(posX, posY);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has
+ * size change.
+ */
+ static class SnapshotAdapter extends ActivityEmbeddingAnimationAdapter {
+
+ SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl snapshotLeash) {
+ super(animation, change, snapshotLeash);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Snapshot should always be placed at the top left of the animation leash.
+ mTransformation.getMatrix().postTranslate(0, 0);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+
+ @Override
+ void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ super.onAnimationEnd(t);
+ // Remove the screenshot leash after animation is finished.
+ t.remove(mLeash);
+ }
+ }
+
+ /**
+ * Should be used for the animation of the {@link TransitionInfo.Change} that has size change.
+ */
+ static class BoundsChangeAdapter extends ActivityEmbeddingAnimationAdapter {
+
+ BoundsChangeAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change) {
+ super(animation, change);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ final Point offset = mChange.getEndRelOffset();
+ mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mVecs[1] = mVecs[2] = 0;
+ mVecs[0] = mVecs[3] = 1;
+ mTransformation.getMatrix().mapVectors(mVecs);
+ mVecs[0] = 1.f / mVecs[0];
+ mVecs[3] = 1.f / mVecs[3];
+ final Rect clipRect = mTransformation.getClipRect();
+ mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+ mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+ mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+ mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+ t.setCrop(mLeash, mRect);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
new file mode 100644
index 000000000000..7e0795d11153
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2022 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.activityembedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.common.ScreenshotUtils;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** To run the ActivityEmbedding animations. */
+class ActivityEmbeddingAnimationRunner {
+
+ private static final String TAG = "ActivityEmbeddingAnimR";
+
+ private final ActivityEmbeddingController mController;
+ @VisibleForTesting
+ final ActivityEmbeddingAnimationSpec mAnimationSpec;
+
+ ActivityEmbeddingAnimationRunner(@NonNull Context context,
+ @NonNull ActivityEmbeddingController controller) {
+ mController = controller;
+ mAnimationSpec = new ActivityEmbeddingAnimationSpec(context);
+ }
+
+ /** Creates and starts animation for ActivityEmbedding transition. */
+ void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final Animator animator = createAnimator(info, startTransaction, finishTransaction,
+ () -> mController.onAnimationFinished(transition));
+ startTransaction.apply();
+ animator.start();
+ }
+
+ /**
+ * Sets transition animation scale settings value.
+ * @param scale The setting value of transition animation scale.
+ */
+ void setAnimScaleSetting(float scale) {
+ mAnimationSpec.setAnimScaleSetting(scale);
+ }
+
+ /** Creates the animator for the given {@link TransitionInfo}. */
+ @VisibleForTesting
+ @NonNull
+ Animator createAnimator(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Runnable animationFinishCallback) {
+ final List<ActivityEmbeddingAnimationAdapter> adapters =
+ createAnimationAdapters(info, startTransaction);
+ long duration = 0;
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ duration = Math.max(duration, adapter.getDurationHint());
+ }
+ final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setDuration(duration);
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+ adapter.onAnimationEnd(t);
+ }
+ t.apply();
+ animationFinishCallback.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ return animator;
+ }
+
+ /**
+ * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window
+ * changes.
+ */
+ @NonNull
+ private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getMode() == TRANSIT_CHANGE
+ && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+ return createChangeAnimationAdapters(info, startTransaction);
+ }
+ }
+ if (Transitions.isClosingType(info.getType())) {
+ return createCloseAnimationAdapters(info);
+ }
+ return createOpenAnimationAdapters(info);
+ }
+
+ @NonNull
+ private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, true /* isOpening */,
+ mAnimationSpec::loadOpenAnimation);
+ }
+
+ @NonNull
+ private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
+ @NonNull TransitionInfo info) {
+ return createOpenCloseAnimationAdapters(info, false /* isOpening */,
+ mAnimationSpec::loadCloseAnimation);
+ }
+
+ /**
+ * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition.
+ * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
+ */
+ @NonNull
+ private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
+ @NonNull TransitionInfo info, boolean isOpening,
+ @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) {
+ // We need to know if the change window is only a partial of the whole animation screen.
+ // If so, we will need to adjust it to make the whole animation screen looks like one.
+ final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
+ final List<TransitionInfo.Change> closingChanges = new ArrayList<>();
+ final Rect openingWholeScreenBounds = new Rect();
+ final Rect closingWholeScreenBounds = new Rect();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final Rect bounds = new Rect(change.getEndAbsBounds());
+ final Point offset = change.getEndRelOffset();
+ bounds.offsetTo(offset.x, offset.y);
+ if (Transitions.isOpeningType(change.getMode())) {
+ openingChanges.add(change);
+ openingWholeScreenBounds.union(bounds);
+ } else {
+ closingChanges.add(change);
+ closingWholeScreenBounds.union(bounds);
+ }
+ }
+
+ // For OPEN transition, open windows should be above close windows.
+ // For CLOSE transition, open windows should be below close windows.
+ int offsetLayer = TYPE_LAYER_OFFSET;
+ final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+ for (TransitionInfo.Change change : openingChanges) {
+ final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
+ change, animationProvider, openingWholeScreenBounds);
+ if (isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
+ }
+ for (TransitionInfo.Change change : closingChanges) {
+ final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
+ change, animationProvider, closingWholeScreenBounds);
+ if (!isOpening) {
+ adapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(adapter);
+ }
+ return adapters;
+ }
+
+ @NonNull
+ private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
+ @NonNull TransitionInfo.Change change,
+ @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
+ @NonNull Rect wholeAnimationBounds) {
+ final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
+ final Rect bounds = new Rect(change.getEndAbsBounds());
+ final Point offset = change.getEndRelOffset();
+ bounds.offsetTo(offset.x, offset.y);
+ if (bounds.left == wholeAnimationBounds.left
+ && bounds.right != wholeAnimationBounds.right) {
+ // This is the left split of the whole animation window.
+ return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
+ true /* isLeftHalf */, wholeAnimationBounds.width());
+ } else if (bounds.left != wholeAnimationBounds.left
+ && bounds.right == wholeAnimationBounds.right) {
+ // This is the right split of the whole animation window.
+ return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
+ false /* isLeftHalf */, wholeAnimationBounds.width());
+ }
+ // Open/close window that fills the whole animation.
+ return new ActivityEmbeddingAnimationAdapter(animation, change);
+ }
+
+ @NonNull
+ private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getMode() == TRANSIT_CHANGE
+ && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+ // This is the window with bounds change.
+ final WindowContainerToken parentToken = change.getParent();
+ final Rect parentBounds;
+ if (parentToken != null) {
+ TransitionInfo.Change parentChange = info.getChange(parentToken);
+ parentBounds = parentChange != null
+ ? parentChange.getEndAbsBounds()
+ : change.getEndAbsBounds();
+ } else {
+ parentBounds = change.getEndAbsBounds();
+ }
+ final Animation[] animations =
+ mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds);
+ // Adapter for the starting screenshot leash.
+ final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction);
+ if (screenshotLeash != null) {
+ // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
+ adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
+ animations[0], change, screenshotLeash));
+ } else {
+ Log.e(TAG, "Failed to take screenshot for change=" + change);
+ }
+ // Adapter for the ending bounds changed leash.
+ adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
+ animations[1], change));
+ continue;
+ }
+
+ // These are the other windows that don't have bounds change in the same transition.
+ final Animation animation;
+ if (!TransitionInfo.isIndependent(change, info)) {
+ // No-op if it will be covered by the changing parent window.
+ animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
+ } else if (Transitions.isClosingType(change.getMode())) {
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ } else {
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ }
+ adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
+ }
+ return adapters;
+ }
+
+ /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */
+ @Nullable
+ private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startTransaction) {
+ final Rect cropBounds = new Rect(change.getStartAbsBounds());
+ cropBounds.offsetTo(0, 0);
+ return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds,
+ Integer.MAX_VALUE);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
new file mode 100644
index 000000000000..6f06f28caff2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 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.activityembedding;
+
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.wm.shell.transition.Transitions;
+
+/** Animation spec for ActivityEmbedding transition. */
+// TODO(b/206557124): provide an easier way to customize animation
+class ActivityEmbeddingAnimationSpec {
+
+ private static final String TAG = "ActivityEmbeddingAnimSpec";
+ private static final int CHANGE_ANIMATION_DURATION = 517;
+ private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
+ private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
+
+ private final Context mContext;
+ private final TransitionAnimation mTransitionAnimation;
+ private final Interpolator mFastOutExtraSlowInInterpolator;
+ private final LinearInterpolator mLinearInterpolator;
+ private float mTransitionAnimationScaleSetting;
+
+ ActivityEmbeddingAnimationSpec(@NonNull Context context) {
+ mContext = context;
+ mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+ mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+ mLinearInterpolator = new LinearInterpolator();
+ }
+
+ /**
+ * Sets transition animation scale settings value.
+ * @param scale The setting value of transition animation scale.
+ */
+ void setAnimScaleSetting(float scale) {
+ mTransitionAnimationScaleSetting = scale;
+ }
+
+ /** For window that doesn't need to be animated. */
+ @NonNull
+ static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) {
+ // Noop but just keep the window showing/hiding.
+ final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f;
+ return new AlphaAnimation(alpha, alpha);
+ }
+
+ /** Animation for window that is opening in a change transition. */
+ @NonNull
+ Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+ final Rect bounds = change.getEndAbsBounds();
+ final Point offset = change.getEndRelOffset();
+ // The window will be animated in from left or right depends on its position.
+ final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /** Animation for window that is closing in a change transition. */
+ @NonNull
+ Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
+ final Rect bounds = change.getEndAbsBounds();
+ final Point offset = change.getEndRelOffset();
+ // The window will be animated out to left or right depends on its position.
+ final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /**
+ * Animation for window that is changing (bounds change) in a change transition.
+ * @return the return array always has two elements. The first one is for the start leash, and
+ * the second one is for the end leash.
+ */
+ @NonNull
+ Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change,
+ @NonNull Rect parentBounds) {
+ // Both start bounds and end bounds are in screen coordinates. We will post translate
+ // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+ final Rect startBounds = change.getStartAbsBounds();
+ final Rect endBounds = change.getEndAbsBounds();
+ float scaleX = ((float) startBounds.width()) / endBounds.width();
+ float scaleY = ((float) startBounds.height()) / endBounds.height();
+ // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+ // be scaled up with its parent.
+ float startScaleX = 1.f / scaleX;
+ float startScaleY = 1.f / scaleY;
+
+ // The start leash will be fade out.
+ final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
+ final Animation startAlpha = new AlphaAnimation(1f, 0f);
+ startAlpha.setInterpolator(mLinearInterpolator);
+ startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+ startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+ startSet.addAnimation(startAlpha);
+ final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+ startScaleY);
+ startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
+ startScale.setDuration(CHANGE_ANIMATION_DURATION);
+ startSet.addAnimation(startScale);
+ startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+ endBounds.height());
+ startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ // The end leash will be moved into the end position while scaling.
+ final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+ endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+ final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+ endScale.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endScale);
+ // The position should be 0-based as we will post translate in
+ // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+ final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+ 0, 0);
+ endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endTranslate);
+ // The end leash is resizing, we should update the window crop based on the clip rect.
+ final Rect startClip = new Rect(startBounds);
+ final Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(clipAnim);
+ endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+ parentBounds.height());
+ endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ return new Animation[]{startSet, endSet};
+ }
+
+ @NonNull
+ Animation loadOpenAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = Transitions.isOpeningType(change.getMode());
+ final Animation animation;
+ // TODO(b/207070762):
+ // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
+ // 2. Implement edgeExtension version
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? R.anim.task_fragment_open_enter
+ : R.anim.task_fragment_open_exit);
+ final Rect bounds = change.getEndAbsBounds();
+ animation.initialize(bounds.width(), bounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ @NonNull
+ Animation loadCloseAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = Transitions.isOpeningType(change.getMode());
+ final Animation animation;
+ // TODO(b/207070762):
+ // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
+ // 2. Implement edgeExtension version
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? R.anim.task_fragment_close_enter
+ : R.anim.task_fragment_close_exit);
+ final Rect bounds = change.getEndAbsBounds();
+ animation.initialize(bounds.width(), bounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b305897b77ae..e0004fcaa060 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -18,8 +18,11 @@ package com.android.wm.shell.activityembedding;
import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+import static java.util.Objects.requireNonNull;
+
import android.content.Context;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -28,6 +31,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -37,15 +41,37 @@ import com.android.wm.shell.transition.Transitions;
public class ActivityEmbeddingController implements Transitions.TransitionHandler {
private final Context mContext;
- private final Transitions mTransitions;
-
- public ActivityEmbeddingController(Context context, ShellInit shellInit,
- Transitions transitions) {
- mContext = context;
- mTransitions = transitions;
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- shellInit.addInitCallback(this::onInit, this);
- }
+ @VisibleForTesting
+ final Transitions mTransitions;
+ @VisibleForTesting
+ final ActivityEmbeddingAnimationRunner mAnimationRunner;
+
+ /**
+ * Keeps track of the currently-running transition callback associated with each transition
+ * token.
+ */
+ private final ArrayMap<IBinder, Transitions.TransitionFinishCallback> mTransitionCallbacks =
+ new ArrayMap<>();
+
+ private ActivityEmbeddingController(@NonNull Context context, @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions) {
+ mContext = requireNonNull(context);
+ mTransitions = requireNonNull(transitions);
+ mAnimationRunner = new ActivityEmbeddingAnimationRunner(context, this);
+
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ /**
+ * Creates {@link ActivityEmbeddingController}, returns {@code null} if the feature is not
+ * supported.
+ */
+ @Nullable
+ public static ActivityEmbeddingController create(@NonNull Context context,
+ @NonNull ShellInit shellInit, @NonNull Transitions transitions) {
+ return Transitions.ENABLE_SHELL_TRANSITIONS
+ ? new ActivityEmbeddingController(context, shellInit, transitions)
+ : null;
}
/** Registers to handle transitions. */
@@ -66,9 +92,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
}
}
- // TODO(b/207070762) Implement AE animation.
- startTransaction.apply();
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ // Start ActivityEmbedding animation.
+ mTransitionCallbacks.put(transition, finishCallback);
+ mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
return true;
}
@@ -79,6 +105,21 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return null;
}
+ @Override
+ public void setAnimScaleSetting(float scale) {
+ mAnimationRunner.setAnimScaleSetting(scale);
+ }
+
+ /** Called when the animation is finished. */
+ void onAnimationFinished(@NonNull IBinder transition) {
+ final Transitions.TransitionFinishCallback callback =
+ mTransitionCallbacks.remove(transition);
+ if (callback == null) {
+ throw new IllegalStateException("No finish callback found");
+ }
+ callback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ }
+
private static boolean isEmbedded(@NonNull TransitionInfo.Change change) {
return (change.getFlags() & FLAG_IS_EMBEDDED) != 0;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 2c02006c8ca5..99b8885acdef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -821,7 +821,7 @@ public class Bubble implements BubbleViewProvider {
/**
* Description of current bubble state.
*/
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ public void dump(@NonNull PrintWriter pw) {
pw.print("key: "); pw.println(mKey);
pw.print(" showInShade: "); pw.println(showInShade());
pw.print(" showDot: "); pw.println(showDot());
@@ -831,7 +831,7 @@ public class Bubble implements BubbleViewProvider {
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
if (mExpandedView != null) {
- mExpandedView.dump(pw, args);
+ mExpandedView.dump(pw);
}
}
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 de26b54971ca..dcbb272feab8 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
@@ -72,7 +72,6 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Log;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -100,6 +99,7 @@ import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -159,6 +159,7 @@ public class BubbleController implements ConfigurationChangeListener {
private final TaskViewTransitions mTaskViewTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -229,6 +230,7 @@ public class BubbleController implements ConfigurationChangeListener {
public BubbleController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -252,6 +254,7 @@ public class BubbleController implements ConfigurationChangeListener {
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mLauncherApps = launcherApps;
mBarService = statusBarService == null
@@ -431,6 +434,7 @@ public class BubbleController implements ConfigurationChangeListener {
mCurrentProfiles = userProfiles;
mShellController.addConfigurationChangeListener(this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
@VisibleForTesting
@@ -538,7 +542,6 @@ public class BubbleController implements ConfigurationChangeListener {
if (mNotifEntryToExpandOnShadeUnlock != null) {
expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
- mNotifEntryToExpandOnShadeUnlock = null;
}
updateStack();
@@ -925,15 +928,6 @@ public class BubbleController implements ConfigurationChangeListener {
return (isSummary && isSuppressedSummary) || isSuppressedBubble;
}
- private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) {
- if (mBubbleData.isSummarySuppressed(groupKey)) {
- mBubbleData.removeSuppressedSummary(groupKey);
- if (callback != null) {
- callback.accept(mBubbleData.getSummaryKey(groupKey));
- }
- }
- }
-
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
@@ -1519,14 +1513,15 @@ public class BubbleController implements ConfigurationChangeListener {
/**
* Description of current bubble state.
*/
- private void dump(PrintWriter pw, String[] args) {
+ private void dump(PrintWriter pw, String prefix) {
pw.println("BubbleController state:");
- mBubbleData.dump(pw, args);
+ mBubbleData.dump(pw);
pw.println();
if (mStackView != null) {
- mStackView.dump(pw, args);
+ mStackView.dump(pw);
}
pw.println();
+ mImpl.mCachedState.dump(pw);
}
/**
@@ -1711,28 +1706,12 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public boolean isStackExpanded() {
- return mCachedState.isStackExpanded();
- }
-
- @Override
@Nullable
public Bubble getBubbleWithShortcutId(String shortcutId) {
return mCachedState.getBubbleWithShortcutId(shortcutId);
}
@Override
- public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- Consumer<String> cb = callback != null
- ? (key) -> callbackExecutor.execute(() -> callback.accept(key))
- : null;
- BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb);
- });
- }
-
- @Override
public void collapseStack() {
mMainExecutor.execute(() -> {
BubbleController.this.collapseStack();
@@ -1761,13 +1740,6 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void openBubbleOverflow() {
- mMainExecutor.execute(() -> {
- BubbleController.this.openBubbleOverflow();
- });
- }
-
- @Override
public boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback,
Executor callbackExecutor) {
@@ -1882,18 +1854,6 @@ public class BubbleController implements ConfigurationChangeListener {
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- try {
- mMainExecutor.executeBlocking(() -> {
- BubbleController.this.dump(pw, args);
- mCachedState.dump(pw);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump BubbleController in 2s");
- }
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index fa86c8436647..c64133f0b668 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -1136,7 +1136,7 @@ public class BubbleData {
/**
* Description of current bubble data state.
*/
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pw) {
pw.print("selected: ");
pw.println(mSelectedBubble != null
? mSelectedBubble.getKey()
@@ -1147,13 +1147,13 @@ public class BubbleData {
pw.print("stack bubble count: ");
pw.println(mBubbles.size());
for (Bubble bubble : mBubbles) {
- bubble.dump(pw, args);
+ bubble.dump(pw);
}
pw.print("overflow bubble count: ");
pw.println(mOverflowBubbles.size());
for (Bubble bubble : mOverflowBubbles) {
- bubble.dump(pw, args);
+ bubble.dump(pw);
}
pw.print("summaryKeys: ");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2666a0e186b3..cfbe1b3caf7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -1044,7 +1044,7 @@ public class BubbleExpandedView extends LinearLayout {
/**
* Description of current expanded view state.
*/
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ public void dump(@NonNull PrintWriter pw) {
pw.print("BubbleExpandedView");
pw.print(" taskId: "); pw.println(mTaskId);
pw.print(" stackView: "); pw.println(mStackView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2d0be066beb5..5bf88b119661 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -299,7 +299,7 @@ public class BubbleStackView extends FrameLayout
private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
/** Description of current animation controller state. */
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pw) {
pw.println("Stack view state:");
String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
@@ -313,8 +313,8 @@ public class BubbleStackView extends FrameLayout
pw.print(" expandedContainerMatrix: ");
pw.println(mExpandedViewContainer.getAnimationMatrix());
- mStackAnimationController.dump(pw, args);
- mExpandedAnimationController.dump(pw, args);
+ mStackAnimationController.dump(pw);
+ mExpandedAnimationController.dump(pw);
if (mExpandedBubble != null) {
pw.println("Expanded bubble state:");
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 37b96ffe5cd1..0e97e9e74114 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
@@ -35,7 +35,6 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
-import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
@@ -91,18 +90,6 @@ public interface Bubbles {
*/
boolean isBubbleExpanded(String key);
- /** @return {@code true} if stack of bubbles is expanded or not. */
- boolean isStackExpanded();
-
- /**
- * Removes a group key indicating that the summary for this group should no longer be
- * suppressed.
- *
- * @param callback If removed, this callback will be called with the summary key of the group
- */
- void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
- Executor callbackExecutor);
-
/** Tell the stack of bubbles to collapse. */
void collapseStack();
@@ -130,9 +117,6 @@ public interface Bubbles {
/** Called for any taskbar changes. */
void onTaskbarChanged(Bundle b);
- /** Open the overflow view. */
- void openBubbleOverflow();
-
/**
* We intercept notification entries (including group summaries) dismissed by the user when
* there is an active bubble associated with it. We do this so that developers can still
@@ -252,9 +236,6 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
- /** Description of current bubble state. */
- void dump(PrintWriter pw, String[] args);
-
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index b521cb6a3d38..ae434bcec6c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -468,7 +468,7 @@ public class ExpandedAnimationController
}
/** Description of current animation controller state. */
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pw) {
pw.println("ExpandedAnimationController state:");
pw.print(" isActive: "); pw.println(isActiveController());
pw.print(" animatingExpand: "); pw.println(mAnimatingExpand);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0a1b4d70fb2b..4e2cbfd82fcc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -431,7 +431,7 @@ public class StackAnimationController extends
}
/** Description of current animation controller state. */
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pw) {
pw.println("StackAnimationController state:");
pw.print(" isActive: "); pw.println(isActiveController());
pw.print(" restingStackPos: ");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index e22c9517f4ab..8022e9b1cd81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -66,6 +66,7 @@ public abstract class TvPipModule {
@Provides
static Optional<Pip> providePip(
Context context,
+ ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -84,6 +85,7 @@ public abstract class TvPipModule {
return Optional.of(
TvPipController.create(
context,
+ shellInit,
shellController,
tvPipBoundsState,
tvPipBoundsAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index a6a04cf67b3c..ceaa64ef8290 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -627,11 +627,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static ActivityEmbeddingController provideActivityEmbeddingController(
+ static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
Context context,
ShellInit shellInit,
Transitions transitions) {
- return new ActivityEmbeddingController(context, shellInit, transitions);
+ return Optional.ofNullable(
+ ActivityEmbeddingController.create(context, shellInit, transitions));
}
//
@@ -686,7 +687,7 @@ public abstract class WMShellBaseModule {
Optional<RecentTasksController> recentTasksOptional,
Optional<OneHandedController> oneHandedControllerOptional,
Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
- ActivityEmbeddingController activityEmbeddingOptional,
+ Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
@ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2bcc134f9dd5..4fe32556a94d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -145,6 +145,7 @@ public abstract class WMShellModule {
@Provides
static BubbleController provideBubbleController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
@@ -165,7 +166,7 @@ public abstract class WMShellModule {
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- return new BubbleController(context, shellInit, shellController, data,
+ return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
null /* synchronizer */, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
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 76c0f41997ad..7129165a78dc 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
@@ -37,16 +37,6 @@ public interface OneHanded {
}
/**
- * Return one handed settings enabled or not.
- */
- boolean isOneHandedEnabled();
-
- /**
- * Return swipe to notification settings enabled or not.
- */
- boolean isSwipeToNotificationEnabled();
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
@@ -80,9 +70,4 @@ public interface OneHanded {
* transition start or finish
*/
void registerTransitionCallback(OneHandedTransitionCallback callback);
-
- /**
- * Notifies when user switch complete
- */
- void onUserSwitch(int userId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 9149204b94ce..e0c4fe8c4fba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -59,6 +59,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
import java.io.PrintWriter;
@@ -67,7 +68,7 @@ import java.io.PrintWriter;
*/
public class OneHandedController implements RemoteCallable<OneHandedController>,
DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
- KeyguardChangeListener {
+ KeyguardChangeListener, UserChangeListener {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -76,8 +77,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
- private volatile boolean mIsOneHandedEnabled;
- private volatile boolean mIsSwipeToNotificationEnabled;
+ private boolean mIsOneHandedEnabled;
+ private boolean mIsSwipeToNotificationEnabled;
private boolean mIsShortcutEnabled;
private boolean mTaskChangeToExit;
private boolean mLockedDisabled;
@@ -294,6 +295,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mState.addSListeners(mTutorialHandler);
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addUserChangeListener(this);
}
public OneHanded asOneHanded() {
@@ -627,7 +629,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
stopOneHanded();
}
- private void onUserSwitch(int newUserId) {
+ @Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
unregisterSettingObservers();
mUserId = newUserId;
registerSettingObservers(newUserId);
@@ -718,18 +721,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
@Override
- public boolean isOneHandedEnabled() {
- // This is volatile so return directly
- return mIsOneHandedEnabled;
- }
-
- @Override
- public boolean isSwipeToNotificationEnabled() {
- // This is volatile so return directly
- return mIsSwipeToNotificationEnabled;
- }
-
- @Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
OneHandedController.this.startOneHanded();
@@ -770,13 +761,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
OneHandedController.this.registerTransitionCallback(callback);
});
}
-
- @Override
- public void onUserSwitch(int userId) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.onUserSwitch(userId);
- });
- }
}
/**
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 93172f82edd1..c06881ae6ad7 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
@@ -51,12 +51,6 @@ public interface Pip {
}
/**
- * Registers the session listener for the current user.
- */
- default void registerSessionListenerForCurrentUser() {
- }
-
- /**
* Sets both shelf visibility and its height.
*
* @param visible visibility of shelf.
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 fc97f310ad4e..ac3407dd1ca1 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
@@ -92,6 +92,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -105,7 +106,8 @@ import java.util.function.Consumer;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements PipTransitionController.PipTransitionCallback,
- RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
+ RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener,
+ UserChangeListener {
private static final String TAG = "PipController";
private Context mContext;
@@ -528,7 +530,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
});
mOneHandedController.ifPresent(controller -> {
- controller.asOneHanded().registerTransitionCallback(
+ controller.registerTransitionCallback(
new OneHandedTransitionCallback() {
@Override
public void onStartFinished(Rect bounds) {
@@ -542,8 +544,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
});
});
+ mMediaController.registerSessionListenerForCurrentUser();
+
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addUserChangeListener(this);
}
@Override
@@ -557,6 +562,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
+ // Re-register the media session listener when switching users
+ mMediaController.registerSessionListenerForCurrentUser();
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
@@ -644,10 +655,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
- private void registerSessionListenerForCurrentUser() {
- mMediaController.registerSessionListenerForCurrentUser();
- }
-
private void onSystemUiStateChanged(boolean isValidState, int flag) {
mTouchHandler.onSystemUiStateChanged(isValidState);
}
@@ -968,13 +975,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void registerSessionListenerForCurrentUser() {
- mMainExecutor.execute(() -> {
- PipController.this.registerSessionListenerForCurrentUser();
- });
- }
-
- @Override
public void setShelfHeight(boolean visible, int height) {
mMainExecutor.execute(() -> {
PipController.this.setShelfHeight(visible, height);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index a24d9618032d..4e1b0469eb96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -32,6 +32,8 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.view.Gravity;
+import androidx.annotation.NonNull;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -51,6 +53,8 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,7 +68,7 @@ import java.util.Set;
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
- ConfigurationChangeListener {
+ ConfigurationChangeListener, UserChangeListener {
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
@@ -105,6 +109,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
+ private final PipTransitionController mPipTransitionController;
+ private final TaskStackListenerImpl mTaskStackListener;
+ private final PipParamsChangedForwarder mPipParamsChangedForwarder;
+ private final DisplayController mDisplayController;
+ private final WindowManagerShellWrapper mWmShellWrapper;
private final ShellExecutor mMainExecutor;
private final TvPipImpl mImpl = new TvPipImpl();
@@ -121,6 +130,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public static Pip create(
Context context,
+ ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -138,6 +148,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellExecutor mainExecutor) {
return new TvPipController(
context,
+ shellInit,
shellController,
tvPipBoundsState,
tvPipBoundsAlgorithm,
@@ -157,6 +168,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private TvPipController(
Context context,
+ ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -170,11 +182,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
- WindowManagerShellWrapper wmShell,
+ WindowManagerShellWrapper wmShellWrapper,
ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mShellController = shellController;
+ mDisplayController = displayController;
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
@@ -193,16 +206,32 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
- pipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController = pipTransitionController;
+ mPipParamsChangedForwarder = pipParamsChangedForwarder;
+ mTaskStackListener = taskStackListener;
+ mWmShellWrapper = wmShellWrapper;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mPipTransitionController.registerPipTransitionCallback(this);
loadConfigurations();
- registerPipParamsChangedListener(pipParamsChangedForwarder);
- registerTaskStackListenerCallback(taskStackListener);
- registerWmShellPinnedStackListener(wmShell);
- displayController.addDisplayWindowListener(this);
+ registerPipParamsChangedListener(mPipParamsChangedForwarder);
+ registerTaskStackListenerCallback(mTaskStackListener);
+ registerWmShellPinnedStackListener(mWmShellWrapper);
+ registerSessionListenerForCurrentUser();
+ mDisplayController.addDisplayWindowListener(this);
mShellController.addConfigurationChangeListener(this);
+ mShellController.addUserChangeListener(this);
+ }
+
+ @Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
+ // Re-register the media session listener when switching users
+ registerSessionListenerForCurrentUser();
}
@Override
@@ -679,11 +708,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
private class TvPipImpl implements Pip {
- @Override
- public void registerSessionListenerForCurrentUser() {
- mMainExecutor.execute(() -> {
- TvPipController.this.registerSessionListenerForCurrentUser();
- });
- }
+ // Not used
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 4d7c8465bcc2..97e017a65b04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -97,6 +97,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
+ private final View mPipBackground;
+ private final View mDimLayer;
+
private final ScrollView mScrollView;
private final HorizontalScrollView mHorizontalScrollView;
private View mFocusedButton;
@@ -148,6 +151,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
mExpandButton.setOnClickListener(this);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
mScrollView = findViewById(R.id.tv_pip_menu_scroll);
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
@@ -231,7 +237,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
finishBounds.width() / (float) finishBounds.height());
if (ratioChanged) {
- mPipView.animate()
+ mPipBackground.animate()
.alpha(1f)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
@@ -272,7 +278,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
"%s: onPipTransitionFinished()", TAG);
// Fade in content by fading out view on top.
- mPipView.animate()
+ mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
.setInterpolator(TvPipInterpolators.ENTER)
@@ -770,6 +776,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
refocusPreviousButton();
}
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+ animateAlphaTo(show ? 1 : 0, mDimLayer);
}
private void setFrameHighlighted(boolean highlighted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
index 1c0b35894acd..9df863163b50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -21,13 +21,13 @@ package com.android.wm.shell.sysui;
*/
public interface KeyguardChangeListener {
/**
- * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+ * Called when the keyguard is showing (and if so, whether it is occluded).
*/
default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
boolean animatingDismiss) {}
/**
- * Notifies the Shell when the keyguard dismiss animation has finished.
+ * Called when the keyguard dismiss animation has finished.
*
* TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
* keyguard dismiss animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 52ffb46bb39c..57993948886b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -25,7 +25,9 @@ import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
+import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
@@ -36,6 +38,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -53,6 +56,9 @@ public class ShellController {
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
+ new CopyOnWriteArrayList<>();
+
private Configuration mLastConfiguration;
@@ -102,6 +108,22 @@ public class ShellController {
mKeyguardChangeListeners.remove(listener);
}
+ /**
+ * Adds a new user-change listener. The user change callbacks are not made in any
+ * particular order.
+ */
+ public void addUserChangeListener(UserChangeListener listener) {
+ mUserChangeListeners.remove(listener);
+ mUserChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing user-change listener.
+ */
+ public void removeUserChangeListener(UserChangeListener listener) {
+ mUserChangeListeners.remove(listener);
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -144,6 +166,8 @@ public class ShellController {
@VisibleForTesting
void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard visibility changed: visible=%b "
+ + "occluded=%b animatingDismiss=%b", visible, occluded, animatingDismiss);
for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
}
@@ -151,17 +175,35 @@ public class ShellController {
@VisibleForTesting
void onKeyguardDismissAnimationFinished() {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard dismiss animation finished");
for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
listener.onKeyguardDismissAnimationFinished();
}
}
+ @VisibleForTesting
+ void onUserChanged(int newUserId, @NonNull Context userContext) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User changed: id=%d", newUserId);
+ for (UserChangeListener listener : mUserChangeListeners) {
+ listener.onUserChanged(newUserId, userContext);
+ }
+ }
+
+ @VisibleForTesting
+ void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User profiles changed");
+ for (UserChangeListener listener : mUserChangeListeners) {
+ listener.onUserProfilesChanged(profiles);
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
+ pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
}
/**
@@ -220,5 +262,17 @@ public class ShellController {
mMainExecutor.execute(() ->
ShellController.this.onKeyguardDismissAnimationFinished());
}
+
+ @Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
+ mMainExecutor.execute(() ->
+ ShellController.this.onUserChanged(newUserId, userContext));
+ }
+
+ @Override
+ public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+ mMainExecutor.execute(() ->
+ ShellController.this.onUserProfilesChanged(profiles));
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 254c253b0042..2108c824ac6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.sysui;
+import android.content.Context;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import androidx.annotation.NonNull;
+
import java.io.PrintWriter;
+import java.util.List;
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -59,4 +64,14 @@ public interface ShellInterface {
* Notifies the Shell when the keyguard dismiss animation has finished.
*/
default void onKeyguardDismissAnimationFinished() {}
+
+ /**
+ * Notifies the Shell when the user changes.
+ */
+ default void onUserChanged(int newUserId, @NonNull Context userContext) {}
+
+ /**
+ * Notifies the Shell when a profile belonging to the user changes.
+ */
+ default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java
new file mode 100644
index 000000000000..3d0909f6128d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.sysui;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Callbacks for when the user or user's profiles changes.
+ */
+public interface UserChangeListener {
+ /**
+ * Called when the current (parent) user changes.
+ */
+ default void onUserChanged(int newUserId, @NonNull Context userContext) {}
+
+ /**
+ * Called when a profile belonging to the user changes.
+ */
+ default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index bdcdb63d2cd6..cc4d268a0000 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -34,4 +34,9 @@ interface IShellTransitions {
* Unregisters a remote transition handler.
*/
oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2;
+
+ /**
+ * Retrieves the apply-token used by transactions in Shell
+ */
+ IBinder getShellApplyToken() = 3;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 26d0ec637ccf..d2e8624171f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -219,6 +219,8 @@ public class Transitions implements RemoteCallable<Transitions> {
+ "use ShellInit callbacks to ensure proper ordering");
}
mHandlers.add(handler);
+ // Set initial scale settings.
+ handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
handler.getClass().getSimpleName());
}
@@ -956,6 +958,11 @@ public class Transitions implements RemoteCallable<Transitions> {
transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
+
+ @Override
+ public IBinder getShellApplyToken() {
+ return SurfaceControl.Transaction.getDefaultApplyToken();
+ }
}
private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 0a54b8c016fb..a8154e818a04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -32,7 +32,6 @@ import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.common.ComponentMatcher
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assume
import org.junit.Test
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 330c9c95e484..cb74315732ab 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
@@ -20,6 +20,7 @@ package com.android.wm.shell.flicker
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.region.Region
@@ -94,25 +95,27 @@ fun FlickerTestParameter.layerKeepVisible(
fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
component: IComponentMatcher,
- splitLeftTop: Boolean
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayers {
- this.isInvisible(component)
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
- .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- .isVisible(component)
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
.then()
- .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ .splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
}
}
fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
component: IComponentMatcher,
- splitLeftTop: Boolean
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayers {
- this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ this.splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
.then()
.isVisible(component, true)
.then()
@@ -122,58 +125,96 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
component: IComponentMatcher,
- splitLeftTop: Boolean
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayersEnd {
- val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(component).coversAtMost(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, endRotation)
- } else {
- getSplitRightBottomRegion(dividerRegion, endRotation)
- }
- )
+ splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
}
}
fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
component: IComponentMatcher,
- splitLeftTop: Boolean
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayers {
- this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
}
}
fun FlickerTestParameter.splitAppLayerBoundsChanges(
component: IComponentMatcher,
- splitLeftTop: Boolean
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean
) {
assertLayers {
- if (splitLeftTop) {
- this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ if (landscapePosLeft) {
+ this.splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
.then()
.isInvisible(component)
.then()
- .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ .splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
} else {
- this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ this.splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
}
}
}
fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
component: IComponentMatcher,
- splitLeftTop: Boolean,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean,
rotation: Int
): LayersTraceSubject {
return invoke("splitAppLayerBoundsSnapToDivider") {
- val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- it.visibleRegion(component).coversAtMost(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
+ it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
+ }
+}
+
+fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
+ component: IComponentMatcher,
+ landscapePosLeft: Boolean,
+ portraitPosTop: Boolean,
+ rotation: Int
+): LayerTraceEntrySubject {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return invoke {
+ val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(component).coversAtMost(
+ if (displayBounds.width > displayBounds.height) {
+ if (landscapePosLeft) {
+ Region.from(
+ 0,
+ 0,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ displayBounds.bounds.bottom)
+ } else {
+ Region.from(
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ 0,
+ displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
} else {
- getSplitRightBottomRegion(dividerRegion, rotation)
+ if (portraitPosTop) {
+ Region.from(
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
+ } else {
+ Region.from(
+ 0,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+ displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
}
)
}
@@ -185,6 +226,10 @@ fun FlickerTestParameter.appWindowBecomesVisible(
assertWm {
this.isAppWindowInvisible(component)
.then()
+ .notContains(component, isOptional = true)
+ .then()
+ .isAppWindowInvisible(component, isOptional = true)
+ .then()
.isAppWindowVisible(component)
}
}
@@ -208,7 +253,7 @@ fun FlickerTestParameter.appWindowIsVisibleAtEnd(
}
fun FlickerTestParameter.appWindowKeepVisible(
- component: IComponentMatcher
+ component: IComponentMatcher
) {
assertWm {
this.isAppWindowVisible(component)
@@ -316,39 +361,3 @@ fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
)
}
}
-
-fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (displayBounds.width > displayBounds.height) {
- Region.from(
- 0,
- 0,
- (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
- displayBounds.bounds.bottom)
- } else {
- Region.from(
- 0,
- 0,
- displayBounds.bounds.right,
- (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
- }
-}
-
-fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (displayBounds.width > displayBounds.height) {
- Region.from(
- (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
- 0,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
- )
- } else {
- Region.from(
- 0,
- (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
- displayBounds.bounds.right,
- displayBounds.bounds.bottom
- )
- }
-}
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 8b717a0cb75e..06361f9ca4ab 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
@@ -17,10 +17,10 @@
@file:JvmName("CommonConstants")
package com.android.wm.shell.flicker
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentMatcher("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = ComponentMatcher("", "DockedStackDivider#")
-val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentMatcher("", "StageCoordinatorSplitDivider#")
-val SPLIT_DECOR_MANAGER = ComponentMatcher("", "SplitDecorManager#")
+val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
+val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
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 ffbac39c6078..826cc2ef16d6 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,10 +17,10 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
- component: IComponentMatcher
+ component: ComponentNameMatcher
) : BaseAppHelper(instrumentation, activityLabel, component)
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 c4379e9a27cc..01ba9907c24c 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
@@ -26,13 +26,13 @@ import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
import java.io.IOException
abstract class BaseAppHelper(
instrumentation: Instrumentation,
launcherName: String,
- component: IComponentMatcher
+ component: IComponentNameMatcher
) : StandardAppHelper(
instrumentation,
launcherName,
@@ -46,9 +46,6 @@ abstract class BaseAppHelper(
hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
}
- val defaultWindowName: String
- get() = toWindowName()
-
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
index 92b1d216f5ab..245a82f938b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
@@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.content.Context
import android.provider.Settings
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
class MultiWindowHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentsInfo: IComponentMatcher
+ componentsInfo: ComponentNameMatcher
) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
companion object {
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 a1226e682e05..e7f9d9a9d73d 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
@@ -28,6 +28,7 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
@@ -37,8 +38,8 @@ import com.android.wm.shell.flicker.testapp.Components
class SplitScreenHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentsInfo: IComponentMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
+ componentInfo: IComponentNameMatcher
+) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
companion object {
const val TEST_REPETITIONS = 1
@@ -104,6 +105,25 @@ class SplitScreenHelper(
.waitForAndVerify()
}
+ fun splitFromOverview(tapl: LauncherInstrumentation) {
+ // Note: The initial split position in landscape is different between tablet and phone.
+ // In landscape, tablet will let the first app split to right side, and phone will
+ // split to left side.
+ if (tapl.isTablet) {
+ tapl.workspace.switchToOverview().overviewActions
+ .clickSplit()
+ .currentTask
+ .open()
+ } else {
+ tapl.workspace.switchToOverview().currentTask
+ .tapMenu()
+ .tapSplitMenuItem()
+ .currentTask
+ .open()
+ }
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
@@ -280,12 +300,12 @@ class SplitScreenHelper(
fun copyContentFromLeftToRight(
instrumentation: Instrumentation,
device: UiDevice,
- sourceApp: IComponentMatcher,
- destinationApp: IComponentMatcher,
+ sourceApp: IComponentNameMatcher,
+ destinationApp: IComponentNameMatcher,
) {
// Copy text from sourceApp
val textView = device.wait(Until.findObject(
- By.res(sourceApp.packageNames.firstOrNull(), "SplitScreenTest")), TIMEOUT_MS)
+ By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
longPress(instrumentation, textView.getVisibleCenter())
val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
@@ -293,7 +313,7 @@ class SplitScreenHelper(
// Paste text to destinationApp
val editText = device.wait(Until.findObject(
- By.res(destinationApp.packageNames.firstOrNull(), "plain_text_input")), TIMEOUT_MS)
+ By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
longPress(instrumentation, editText.getVisibleCenter())
val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
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 045022414fe0..d194472b9000 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
@@ -25,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -155,9 +155,9 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
@Test
fun launcherLayerBecomesVisible() {
testSpec.assertLayers {
- isInvisible(ComponentMatcher.LAUNCHER)
+ isInvisible(ComponentNameMatcher.LAUNCHER)
.then()
- .isVisible(ComponentMatcher.LAUNCHER)
+ .isVisible(ComponentNameMatcher.LAUNCHER)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index dff447b97b9b..507562b00f4f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -30,7 +30,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 33f787182868..fd1fe65fa3a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -23,7 +23,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
import org.junit.Test
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 5b5b9fc174f6..31a39c190dd6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -24,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,9 +63,9 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
val barComponent = if (testSpec.isTablet) {
- ComponentMatcher.TASK_BAR
+ ComponentNameMatcher.TASK_BAR
} else {
- ComponentMatcher.NAV_BAR
+ ComponentNameMatcher.NAV_BAR
}
val barLayerHeight = wmHelper.currentState.layerState
.getLayerWithBuffer(barComponent)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 1c0bd0caa901..fd661cfc3ee1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -25,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -151,7 +151,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
@Test
fun launcherIsAlwaysVisible() {
testSpec.assertLayers {
- isVisible(ComponentMatcher.LAUNCHER)
+ isVisible(ComponentNameMatcher.LAUNCHER)
}
}
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 911d402cfde7..454927e57aa2 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
@@ -28,7 +28,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import org.junit.Assume.assumeFalse
import org.junit.Before
@@ -105,7 +105,7 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Test
open fun pipIsAboveAppWindow() {
testSpec.assertWmTag(TAG_IME_VISIBLE) {
- isAboveWindow(ComponentMatcher.IME, pipApp)
+ isAboveWindow(ComponentNameMatcher.IME, pipApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index f69107eae638..d23881475ad6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -92,12 +92,12 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
@Presubmit
@Test
fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
- primaryApp, splitLeftTop = true)
+ primaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
- textEditApp, splitLeftTop = false)
+ textEditApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index cd92db74af95..ba40c2740bb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -98,7 +98,7 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
@Presubmit
@Test
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 127ac1e7162b..6828589656d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -96,12 +96,12 @@ class DismissSplitScreenByGoHome(
@Presubmit
@Test
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 0f4d98d69c00..9ac7c230096b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -108,12 +108,12 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes
@Presubmit
@Test
fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 9564d975194b..8401c1a910b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -94,12 +94,12 @@ class EnterSplitScreenByDragFromAllApps(
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 3b59716180b6..168afda119a9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -109,12 +109,12 @@ class EnterSplitScreenByDragFromNotification(
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- sendNotificationApp, splitLeftTop = true)
+ sendNotificationApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 3de98723e132..c1fce5f40b57 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -97,12 +97,12 @@ class EnterSplitScreenByDragFromTaskbar(
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
new file mode 100644
index 000000000000..8cb5d7c24ced
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 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.Postsubmit
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen from Overview.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ SplitScreenHelper.splitFromOverview(tapl)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index bdfd9c7de32f..153056188d24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -94,12 +94,12 @@ class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreen
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = true)
+ primaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, splitLeftTop = false)
+ secondaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index da954d97aec2..20544bd2fc2f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -98,12 +98,12 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index db89ff52178b..5a8604f2dccc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -97,12 +97,12 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index c23cdb610671..adea66a49c46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -99,12 +99,12 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, splitLeftTop = false)
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, splitLeftTop = true)
+ secondaryApp, landscapePosLeft = true, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
new file mode 100644
index 000000000000..b2e45a6b3a5c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.activityembedding;
+
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.window.TransitionInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for {@link ActivityEmbeddingAnimationRunner}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
+
+ @Before
+ public void setup() {
+ super.setUp();
+ doNothing().when(mController).onAnimationFinished(any());
+ }
+
+ @Test
+ public void testStartAnimation() {
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createChange();
+ embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ info.addChange(embeddingChange);
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+
+ mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
+
+ final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
+ verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
+ finishCallback.capture());
+ verify(mStartTransaction).apply();
+ verify(mAnimator).start();
+ verifyNoMoreInteractions(mFinishTransaction);
+ verify(mController, never()).onAnimationFinished(any());
+
+ // Call onAnimationFinished() when the animation is finished.
+ finishCallback.getValue().run();
+
+ verify(mController).onAnimationFinished(mTransition);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
new file mode 100644
index 000000000000..84befdddabdb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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.activityembedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.mock;
+
+import android.animation.Animator;
+import android.annotation.CallSuper;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** TestBase for ActivityEmbedding animation. */
+abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
+
+ @Mock
+ ShellInit mShellInit;
+ @Mock
+ Transitions mTransitions;
+ @Mock
+ IBinder mTransition;
+ @Mock
+ SurfaceControl.Transaction mStartTransaction;
+ @Mock
+ SurfaceControl.Transaction mFinishTransaction;
+ @Mock
+ Transitions.TransitionFinishCallback mFinishCallback;
+ @Mock
+ Animator mAnimator;
+
+ ActivityEmbeddingController mController;
+ ActivityEmbeddingAnimationRunner mAnimRunner;
+ ActivityEmbeddingAnimationSpec mAnimSpec;
+
+ @CallSuper
+ @Before
+ public void setUp() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ MockitoAnnotations.initMocks(this);
+ mController = ActivityEmbeddingController.create(mContext, mShellInit, mTransitions);
+ assertNotNull(mController);
+ mAnimRunner = mController.mAnimationRunner;
+ assertNotNull(mAnimRunner);
+ mAnimSpec = mAnimRunner.mAnimationSpec;
+ assertNotNull(mAnimSpec);
+ spyOn(mController);
+ spyOn(mAnimRunner);
+ spyOn(mAnimSpec);
+ }
+
+ /** Creates a mock {@link TransitionInfo.Change}. */
+ static TransitionInfo.Change createChange() {
+ return new TransitionInfo.Change(mock(WindowContainerToken.class),
+ mock(SurfaceControl.class));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index bfe3b5468085..cf43b0030d2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -16,52 +16,117 @@
package com.android.wm.shell.activityembedding;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
-import static org.junit.Assume.assumeTrue;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
-import android.content.Context;
+import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
/**
- * Tests for the activity embedding controller.
+ * Tests for {@link ActivityEmbeddingController}.
*
* Build/Install/Run:
* atest WMShellUnitTests:ActivityEmbeddingControllerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class ActivityEmbeddingControllerTests extends ShellTestCase {
-
- private @Mock Context mContext;
- private @Mock ShellInit mShellInit;
- private @Mock Transitions mTransitions;
- private ActivityEmbeddingController mController;
+public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase {
@Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions));
+ public void setup() {
+ super.setUp();
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
}
@Test
- public void instantiate_addInitCallback() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ public void testInstantiate() {
+ verify(mShellInit).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void testOnInit() {
+ mController.onInit();
+
+ verify(mTransitions).addHandler(mController);
+ }
+
+ @Test
+ public void testSetAnimScaleSetting() {
+ mController.setAnimScaleSetting(1.0f);
+
+ verify(mAnimRunner).setAnimScaleSetting(1.0f);
+ verify(mAnimSpec).setAnimScaleSetting(1.0f);
+ }
+
+ @Test
+ public void testStartAnimation_containsNonActivityEmbeddingChange() {
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createChange();
+ embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ final TransitionInfo.Change nonEmbeddingChange = createChange();
+ info.addChange(embeddingChange);
+ info.addChange(nonEmbeddingChange);
+
+ // No-op
+ assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
+ verifyNoMoreInteractions(mStartTransaction);
+ verifyNoMoreInteractions(mFinishTransaction);
+ verifyNoMoreInteractions(mFinishCallback);
+ }
+
+ @Test
+ public void testStartAnimation_onlyActivityEmbeddingChange() {
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createChange();
+ embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ info.addChange(embeddingChange);
+
+ // No-op
+ assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction);
+ verify(mStartTransaction).apply();
+ verifyNoMoreInteractions(mFinishTransaction);
+ }
+
+ @Test
+ public void testOnAnimationFinished() {
+ // Should not call finish when there is no transition.
+ assertThrows(IllegalStateException.class,
+ () -> mController.onAnimationFinished(mTransition));
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createChange();
+ embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ info.addChange(embeddingChange);
+ mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback);
+
+ verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+ mController.onAnimationFinished(mTransition);
+ verify(mFinishCallback).onTransitionFinished(any(), any());
+
+ // Should not call finish when the finish has already been called.
+ assertThrows(IllegalStateException.class,
+ () -> mController.onAnimationFinished(mTransition));
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 90645ce4747d..cf8297eec061 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -171,6 +171,11 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testControllerRegistersUserChangeListener() {
+ verify(mMockShellController, times(1)).addUserChangeListener(any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
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 9ed8d84d665f..eb5726bebb74 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
@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -77,9 +78,9 @@ import java.util.Set;
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
private ShellInit mShellInit;
+ private ShellController mShellController;
@Mock private ShellCommandHandler mMockShellCommandHandler;
- @Mock private ShellController mMockShellController;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@@ -110,8 +111,10 @@ public class PipControllerTest extends ShellTestCase {
return null;
}).when(mMockExecutor).execute(any());
mShellInit = spy(new ShellInit(mMockExecutor));
+ mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler,
+ mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
- mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mShellController, mMockDisplayController, mMockPipAppOpsListener,
mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
@@ -135,12 +138,22 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registerConfigChangeListener() {
- verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
+ verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@Test
public void instantiatePipController_registerKeyguardChangeListener() {
- verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ verify(mShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
+ public void instantiatePipController_registerUserChangeListener() {
+ verify(mShellController, times(1)).addUserChangeListener(any());
+ }
+
+ @Test
+ public void instantiatePipController_registerMediaListener() {
+ verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser();
}
@Test
@@ -167,7 +180,7 @@ public class PipControllerTest extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMockExecutor);
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
- mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mShellController, mMockDisplayController, mMockPipAppOpsListener,
mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
@@ -264,4 +277,11 @@ public class PipControllerTest extends ShellTestCase {
verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of());
}
+
+ @Test
+ public void onUserChangeRegisterMediaListener() {
+ reset(mMockPipMediaController);
+ mShellController.asShell().onUserChanged(100, mContext);
+ verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser();
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 39e58ffcf9c7..d6ddba9e927d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -17,11 +17,15 @@
package com.android.wm.shell.sysui;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import android.content.Context;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -35,6 +39,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
@SmallTest
@@ -42,22 +48,29 @@ import java.util.Locale;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ShellControllerTest extends ShellTestCase {
+ private static final int TEST_USER_ID = 100;
+
@Mock
private ShellInit mShellInit;
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private ShellExecutor mExecutor;
+ @Mock
+ private Context mTestUserContext;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
+ private TestUserChangeListener mUserChangeListener;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
+ mUserChangeListener = new TestUserChangeListener();
mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -68,6 +81,46 @@ public class ShellControllerTest extends ShellTestCase {
}
@Test
+ public void testAddUserChangeListener_ensureCallback() {
+ mController.addUserChangeListener(mUserChangeListener);
+
+ mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+ assertTrue(mUserChangeListener.userChanged == 1);
+ assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
+ }
+
+ @Test
+ public void testDoubleAddUserChangeListener_ensureSingleCallback() {
+ mController.addUserChangeListener(mUserChangeListener);
+ mController.addUserChangeListener(mUserChangeListener);
+
+ mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+ assertTrue(mUserChangeListener.userChanged == 1);
+ assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
+ }
+
+ @Test
+ public void testAddRemoveUserChangeListener_ensureNoCallback() {
+ mController.addUserChangeListener(mUserChangeListener);
+ mController.removeUserChangeListener(mUserChangeListener);
+
+ mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+ assertTrue(mUserChangeListener.userChanged == 0);
+ assertTrue(mUserChangeListener.lastUserContext == null);
+ }
+
+ @Test
+ public void testUserProfilesChanged() {
+ mController.addUserChangeListener(mUserChangeListener);
+
+ ArrayList<UserInfo> profiles = new ArrayList<>();
+ profiles.add(mock(UserInfo.class));
+ profiles.add(mock(UserInfo.class));
+ mController.onUserProfilesChanged(profiles);
+ assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles));
+ }
+
+ @Test
public void testAddKeyguardChangeListener_ensureCallback() {
mController.addKeyguardChangeListener(mKeyguardChangeListener);
@@ -332,4 +385,27 @@ public class ShellControllerTest extends ShellTestCase {
dismissAnimationFinished++;
}
}
+
+ private class TestUserChangeListener implements UserChangeListener {
+ // Counts of number of times each of the callbacks are called
+ public int userChanged;
+ public int lastUserId;
+ public Context lastUserContext;
+ public int userProfilesChanged;
+ public List<? extends UserInfo> lastUserProfiles;
+
+
+ @Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
+ userChanged++;
+ lastUserId = newUserId;
+ lastUserContext = userContext;
+ }
+
+ @Override
+ public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+ userProfilesChanged++;
+ lastUserProfiles = profiles;
+ }
+ }
}
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 9a4bda2ee1df..3c67edc9a428 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -125,9 +125,14 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
}
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
- int weight, int italic) {
+ int weight, int italic, const Typeface* fallback) {
Typeface* result = new Typeface;
- result->fFontCollection = minikin::FontCollection::create(families);
+ if (fallback == nullptr) {
+ result->fFontCollection = minikin::FontCollection::create(std::move(families));
+ } else {
+ result->fFontCollection =
+ fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
+ }
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 0c3ef01ab26b..565136e53676 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -78,7 +78,8 @@ public:
Typeface* src, const std::vector<minikin::FontVariation>& variations);
static Typeface* createFromFamilies(
- std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic);
+ std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
+ const Typeface* fallback);
static void setDefault(const Typeface* face);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index f5ed5689e4e8..209b35c5537c 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -109,27 +109,14 @@ static jint Typeface_getWeight(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
jlong fallbackPtr, int weight, int italic) {
ScopedLongArrayRO families(env, familyArray);
- std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
- if (typeface != nullptr) {
- const std::shared_ptr<minikin::FontCollection>& fallbackCollection =
- toTypeface(fallbackPtr)->fFontCollection;
- familyVec.reserve(families.size() + fallbackCollection->getFamilyCount());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
- for (size_t i = 0; i < fallbackCollection->getFamilyCount(); i++) {
- familyVec.emplace_back(fallbackCollection->getFamilyAt(i));
- }
- } else {
- familyVec.reserve(families.size());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
+ std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
+ familyVec.reserve(families.size());
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+ familyVec.emplace_back(family->family);
}
- return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
+ return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface));
}
// CriticalNative
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 81bcb4e609df..c5196eeccea3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -54,8 +54,6 @@ typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore
#include <SkColorSpace.h>
#include <SkRefCnt.h>
-class GrVkExtensions;
-
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 25cc8ca0dafb..499afa039d1f 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -73,7 +73,8 @@ std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const ch
TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
// Keep the original to restore it later.
@@ -351,24 +352,24 @@ TEST(TypefaceTest, createAbsolute) {
TEST(TypefaceTest, createFromFamilies_Single) {
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
- std::unique_ptr<Typeface> regular(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
+ std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
- std::unique_ptr<Typeface> bold(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
+ std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
- std::unique_ptr<Typeface> italic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
+ std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -376,8 +377,8 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -385,8 +386,8 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
- std::unique_ptr<Typeface> over1000(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
+ std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
EXPECT_EQ(1000, over1000->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
@@ -394,30 +395,33 @@ TEST(TypefaceTest, createFromFamilies_Single) {
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
// In Java, new Typeface.Builder("Family-Regular.ttf").build();
- std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new Typeface.Builder("Family-Bold.ttf").build();
- std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
- makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> bold(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new Typeface.Builder("Family-Italic.ttf").build();
- std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
- makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> italic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
// In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -427,8 +431,9 @@ TEST(TypefaceTest, createFromFamilies_Family) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
@@ -436,10 +441,24 @@ TEST(TypefaceTest, createFromFamilies_Family) {
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
+TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
+ std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = {
+ buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
+ std::unique_ptr<Typeface> fallback(
+ Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, fallback.get()));
+ EXPECT_EQ(400, regular->fStyle.weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+}
+
} // namespace