diff options
Diffstat (limited to 'libs')
35 files changed, 961 insertions, 480 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml deleted file mode 100644 index d8f356164358..000000000000 --- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="@color/tv_pip_menu_focus_border" - android:pathData="M7,10l5,5 5,-5H7z"/> -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml deleted file mode 100644 index 3e0011c65942..000000000000 --- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="@color/tv_pip_menu_focus_border" - android:pathData="M14,7l-5,5 5,5V7z"/> -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml deleted file mode 100644 index f6b3c72e3cb5..000000000000 --- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="@color/tv_pip_menu_focus_border" - android:pathData="M10,17l5,-5 -5,-5v10z"/> -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml deleted file mode 100644 index 1a3446249573..000000000000 --- a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="@color/tv_pip_menu_focus_border" - android:pathData="M7,14l5,-5 5,5H7z"/> -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index ab64f9e359b0..82a358cf68d6 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -104,9 +104,7 @@ android:layout_centerHorizontal="true" android:layout_alignParentTop="true" android:alpha="0" - android:contentDescription="@string/a11y_action_pip_move_up" - android:elevation="@dimen/pip_menu_arrow_elevation" - android:src="@drawable/pip_ic_move_up" /> + android:contentDescription="@string/a11y_action_pip_move_up"/> <ImageView android:id="@+id/tv_pip_menu_arrow_right" @@ -115,9 +113,7 @@ android:layout_centerVertical="true" android:layout_alignParentRight="true" android:alpha="0" - android:contentDescription="@string/a11y_action_pip_move_right" - android:elevation="@dimen/pip_menu_arrow_elevation" - android:src="@drawable/pip_ic_move_right" /> + android:contentDescription="@string/a11y_action_pip_move_right"/> <ImageView android:id="@+id/tv_pip_menu_arrow_down" @@ -126,9 +122,7 @@ android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:alpha="0" - android:contentDescription="@string/a11y_action_pip_move_down" - android:elevation="@dimen/pip_menu_arrow_elevation" - android:src="@drawable/pip_ic_move_down" /> + android:contentDescription="@string/a11y_action_pip_move_down"/> <ImageView android:id="@+id/tv_pip_menu_arrow_left" @@ -137,7 +131,5 @@ android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:alpha="0" - android:contentDescription="@string/a11y_action_pip_move_left" - android:elevation="@dimen/pip_menu_arrow_elevation" - android:src="@drawable/pip_ic_move_left" /> + android:contentDescription="@string/a11y_action_pip_move_left"/> </RelativeLayout> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 7814b7d38fed..6d19e55217f6 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -79,7 +79,7 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string> - <string name="restart_button_description" msgid="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Pour obtenir un meilleur affichage, touchez pour redémarrer cette application."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index ed0cdb61dacf..04ee54046439 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -79,7 +79,7 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string> - <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string> + <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ver melhor."</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string> diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml index adbf65648dd1..fd825639f1e8 100644 --- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml +++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml @@ -33,8 +33,8 @@ <!-- outer space minus border width --> <dimen name="pip_menu_outer_space_frame">20dp</dimen> - <dimen name="pip_menu_arrow_size">24dp</dimen> - <dimen name="pip_menu_arrow_elevation">5dp</dimen> + <dimen name="pip_menu_arrow_size">12dp</dimen> + <dimen name="pip_menu_arrow_elevation">1dp</dimen> <dimen name="pip_menu_elevation_no_menu">1dp</dimen> <dimen name="pip_menu_elevation_move_menu">7dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml index e6933ca3fce6..5f7fb12c3002 100644 --- a/libs/WindowManager/Shell/res/values/colors_tv.xml +++ b/libs/WindowManager/Shell/res/values/colors_tv.xml @@ -27,6 +27,10 @@ <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> + <!-- Normally, the arrow color would be the same as the focus border color. But due to + optical illusion that looks too dark on the screen. That's why we define a separate + (lighter) arrow color. --> + <color name="tv_pip_menu_arrow_color">#F1F3F4</color> <color name="tv_pip_edu_text">#99D2E3FC</color> <color name="tv_pip_edu_text_home_icon">#D2E3FC</color> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 53a438ec2fde..c98090638010 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -54,10 +54,42 @@ public interface BackAnimation { void setTriggerBack(boolean triggerBack); /** - * Sets the threshold values that defining edge swipe behavior. - * @param progressThreshold the max threshold to keep linear progressing back animation. + * Sets the threshold values that define edge swipe behavior.<br> + * <br> + * <h1>How does {@code nonLinearFactor} work?</h1> + * <pre> + * screen screen screen + * width width width + * |——————| |————————————| |————————————————————| + * A B A B C A + * 1 +——————+—————+ 1 +————————————+ 1 +————————————+———————+ + * | / | | —/| | | —————/| + * | / | | —/ | | ——/ | + * | / | | —/ | | ——/ | | + * | / | | —/ | | ——/ | | + * | / | | —/ | | ——/ | | + * |/ | |—/ | |—/ | | + * 0 +————————————+ 0 +————————————+ 0 +————————————+———————+ + * B B B + * </pre> + * Three devices with different widths (smaller, equal, and wider) relative to the progress + * threshold are shown in the graphs.<br> + * - A is the width of the screen<br> + * - B is the progress threshold (horizontal swipe distance where progress is linear)<br> + * - C equals B + (A - B) * nonLinearFactor<br> + * <br> + * If A is less than or equal to B, {@code progress} for the swipe distance between:<br> + * - [0, A] will scale linearly between [0, 1].<br> + * If A is greater than B, {@code progress} for swipe distance between:<br> + * - [0, B] will scale linearly between [0, B / C]<br> + * - (B, A] will scale non-linearly and reach 1. + * + * @param linearDistance up to this distance progress continues linearly. B in the graph above. + * @param maxDistance distance at which the progress will be 1f. A in the graph above. + * @param nonLinearFactor This value is used to calculate the target if the screen is wider + * than the progress threshold. */ - void setSwipeThresholds(float progressThreshold); + void setSwipeThresholds(float linearDistance, float maxDistance, float nonLinearFactor); /** * Sets the system bar listener to control the system bar color. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 6d879b830e2e..bb543f24a8ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -301,9 +301,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } @Override - public void setSwipeThresholds(float progressThreshold) { + public void setSwipeThresholds( + float linearDistance, + float maxDistance, + float nonLinearFactor) { mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds( - progressThreshold)); + linearDistance, maxDistance, nonLinearFactor)); } @Override @@ -509,7 +512,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // Constraints - absolute values float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); - float maxX = mTouchTracker.getMaxX(); // px + float maxX = mTouchTracker.getMaxDistance(); // px float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px // Current state @@ -605,8 +608,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTouchTracker.setTriggerBack(triggerBack); } - private void setSwipeThresholds(float progressThreshold) { - mTouchTracker.setProgressThreshold(progressThreshold); + private void setSwipeThresholds( + float linearDistance, + float maxDistance, + float nonLinearFactor) { + mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor); } private void invokeOrCancelBack() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 7a00f5b9bab4..a0ada39b459e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -28,11 +28,13 @@ import android.window.BackMotionEvent; * Helper class to record the touch location for gesture and generate back events. */ class TouchTracker { - private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP = - "persist.wm.debug.predictive_back_progress_threshold"; - private static final int PROGRESS_THRESHOLD = SystemProperties - .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1); - private float mProgressThreshold; + private static final String PREDICTIVE_BACK_LINEAR_DISTANCE_PROP = + "persist.wm.debug.predictive_back_linear_distance"; + private static final int LINEAR_DISTANCE = SystemProperties + .getInt(PREDICTIVE_BACK_LINEAR_DISTANCE_PROP, -1); + private float mLinearDistance = LINEAR_DISTANCE; + private float mMaxDistance; + private float mNonLinearFactor; /** * Location of the latest touch event */ @@ -125,17 +127,42 @@ class TouchTracker { // the location everytime back is restarted after being cancelled. float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; float deltaX = Math.abs(startX - touchX); - float maxX = getMaxX(); - maxX = maxX == 0 ? 1 : maxX; - return MathUtils.constrain(deltaX / maxX, 0, 1); + float linearDistance = mLinearDistance; + float maxDistance = getMaxDistance(); + maxDistance = maxDistance == 0 ? 1 : maxDistance; + float progress; + if (linearDistance < maxDistance) { + // Up to linearDistance it behaves linearly, then slowly reaches 1f. + + // maxDistance is composed of linearDistance + nonLinearDistance + float nonLinearDistance = maxDistance - linearDistance; + float initialTarget = linearDistance + nonLinearDistance * mNonLinearFactor; + + boolean isLinear = deltaX <= linearDistance; + if (isLinear) { + progress = deltaX / initialTarget; + } else { + float nonLinearDeltaX = deltaX - linearDistance; + float nonLinearProgress = nonLinearDeltaX / nonLinearDistance; + float currentTarget = MathUtils.lerp( + /* start = */ initialTarget, + /* stop = */ maxDistance, + /* amount = */ nonLinearProgress); + progress = deltaX / currentTarget; + } + } else { + // Always linear behavior. + progress = deltaX / maxDistance; + } + return MathUtils.constrain(progress, 0, 1); } /** - * Maximum X value (in pixels). + * Maximum distance in pixels. * Progress is considered to be completed (1f) when this limit is exceeded. */ - float getMaxX() { - return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; + float getMaxDistance() { + return mMaxDistance; } BackMotionEvent createProgressEvent(float progress) { @@ -149,7 +176,14 @@ class TouchTracker { /* departingAnimationTarget = */ null); } - public void setProgressThreshold(float progressThreshold) { - mProgressThreshold = progressThreshold; + public void setProgressThresholds(float linearDistance, float maxDistance, + float nonLinearFactor) { + if (LINEAR_DISTANCE >= 0) { + mLinearDistance = LINEAR_DISTANCE; + } else { + mLinearDistance = linearDistance; + } + mMaxDistance = maxDistance; + mNonLinearFactor = nonLinearFactor; } } 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 698681029595..3eb9fa2eef6b 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 @@ -17,18 +17,12 @@ package com.android.wm.shell.bubbles; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; -import static android.content.pm.ActivityInfo.CONFIG_DENSITY; -import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE; -import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION; -import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; @@ -53,7 +47,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -81,6 +74,7 @@ import android.util.Pair; import android.util.SparseArray; import android.view.IWindowManager; import android.view.SurfaceControl; +import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.WindowInsets; @@ -108,7 +102,6 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.annotations.ExternalMainThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; @@ -116,6 +109,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; 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; @@ -141,7 +135,7 @@ import java.util.function.IntConsumer; * * The controller manages addition, removal, and visible state of bubbles on screen. */ -public class BubbleController implements ComponentCallbacks2, +public class BubbleController implements ConfigurationChangeListener, RemoteCallable<BubbleController> { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -159,6 +153,7 @@ public class BubbleController implements ComponentCallbacks2, private static final boolean BUBBLE_BAR_ENABLED = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); + /** * Common interface to send updates to bubble views. */ @@ -242,17 +237,17 @@ public class BubbleController implements ComponentCallbacks2, /** Whether or not the BubbleStackView has been added to the WindowManager. */ private boolean mAddedToWindowManager = false; - /** - * Saved configuration, used to detect changes in - * {@link #onConfigurationChanged(Configuration)} - */ - private final Configuration mLastConfiguration = new Configuration(); + /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */ + private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED; - /** - * Saved screen bounds, used to detect screen size changes in - * {@link #onConfigurationChanged(Configuration)}. - */ - private final Rect mScreenBounds = new Rect(); + /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/ + private Rect mScreenBounds = new Rect(); + + /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */ + private float mFontScale = 0; + + /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */ + private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED; /** Saved insets, used to detect WindowInset changes. */ private WindowInsets mWindowInsets; @@ -298,8 +293,7 @@ public class BubbleController implements ComponentCallbacks2, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue, IWindowManager wmService) { - mContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null); - mLastConfiguration.setTo(mContext.getResources().getConfiguration()); + mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; mLauncherApps = launcherApps; @@ -323,11 +317,11 @@ public class BubbleController implements ComponentCallbacks2, mBubblePositioner = positioner; mBubbleData = data; mSavedUserBubbleData = new SparseArray<>(); - mBubbleIconFactory = new BubbleIconFactory(mContext, - mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size), - mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), - mContext.getResources().getColor(R.color.important_conversation), - mContext.getResources().getDimensionPixelSize( + mBubbleIconFactory = new BubbleIconFactory(context, + context.getResources().getDimensionPixelSize(R.dimen.bubble_size), + context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + context.getResources().getColor(R.color.important_conversation), + context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; @@ -488,6 +482,7 @@ public class BubbleController implements ComponentCallbacks2, } mCurrentProfiles = userProfiles; + mShellController.addConfigurationChangeListener(this); mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); @@ -779,7 +774,6 @@ public class BubbleController implements ComponentCallbacks2, try { mAddedToWindowManager = true; registerBroadcastReceiver(); - mContext.registerComponentCallbacks(this); mBubbleData.getOverflow().initialize(this); // (TODO: b/273314541) some duplication in the inset listener if (isShowingAsBubbleBar()) { @@ -837,7 +831,6 @@ public class BubbleController implements ComponentCallbacks2, // Put on background for this binder call, was causing jank mBackgroundExecutor.execute(() -> { try { - mContext.unregisterComponentCallbacks(this); mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) { // Not sure if this happens in production, but was happening in tests @@ -937,7 +930,8 @@ public class BubbleController implements ComponentCallbacks2, mSavedUserBubbleData.remove(userId); } - private void onThemeChanged() { + @Override + public void onThemeChanged() { if (mStackView != null) { mStackView.onThemeChanged(); } @@ -969,60 +963,34 @@ public class BubbleController implements ComponentCallbacks2, } } - // Note: Component callbacks are always called on the main thread of the process - @ExternalMainThread @Override public void onConfigurationChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - final int diff = newConfig.diff(mLastConfiguration); - final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0 - || (diff & CONFIG_UI_MODE) != 0; - if (themeChanged) { - onThemeChanged(); - } - if (mBubblePositioner != null) { - mBubblePositioner.update(); + if (mBubblePositioner != null) { + mBubblePositioner.update(); + } + if (mStackView != null && newConfig != null) { + if (newConfig.densityDpi != mDensityDpi + || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) { + mDensityDpi = newConfig.densityDpi; + mScreenBounds.set(newConfig.windowConfiguration.getBounds()); + mBubbleData.onMaxBubblesChanged(); + mBubbleIconFactory = new BubbleIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size), + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + mContext.getResources().getColor(R.color.important_conversation), + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)); + mStackView.onDisplaySizeChanged(); + } + if (newConfig.fontScale != mFontScale) { + mFontScale = newConfig.fontScale; + mStackView.updateFontScale(); + } + if (newConfig.getLayoutDirection() != mLayoutDirection) { + mLayoutDirection = newConfig.getLayoutDirection(); + mStackView.onLayoutDirectionChanged(mLayoutDirection); } - if (mStackView != null) { - final boolean densityChanged = (diff & CONFIG_DENSITY) != 0; - final boolean fontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0; - final boolean layoutDirectionChanged = (diff & CONFIG_LAYOUT_DIRECTION) != 0; - if (densityChanged - || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) { - mScreenBounds.set(newConfig.windowConfiguration.getBounds()); - mBubbleData.onMaxBubblesChanged(); - mBubbleIconFactory = new BubbleIconFactory(mContext, - mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size), - mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_badge_size), - mContext.getResources().getColor(R.color.important_conversation), - mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.importance_ring_stroke_width)); - mStackView.onDisplaySizeChanged(); - } - if (fontScaleChanged) { - mStackView.updateFontScale(); - } - if (layoutDirectionChanged) { - mStackView.onLayoutDirectionChanged(newConfig.getLayoutDirection()); - } - } - mLastConfiguration.setTo(newConfig); - }); - } - - // Note: Component callbacks are always called on the main thread of the process - @ExternalMainThread - @Override - public void onTrimMemory(int level) { - // Do nothing - } - - // Note: Component callbacks are always called on the main thread of the process - @ExternalMainThread - @Override - public void onLowMemory() { - // Do nothing + } } private void onNotificationPanelExpandedChanged(boolean expanded) { 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 adc0c9c4322a..9fcd207dc370 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 @@ -46,6 +46,7 @@ import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.PointF; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; @@ -479,13 +480,18 @@ public class BubbleExpandedView extends LinearLayout { void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, - com.android.internal.R.attr.materialColorSurfaceBright}); + com.android.internal.R.attr.materialColorSurfaceBright, + com.android.internal.R.attr.materialColorSurfaceContainerHigh}); boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; mBackgroundColorFloating = ta.getColor(1, Color.WHITE); mExpandedViewContainer.setBackgroundColor(mBackgroundColorFloating); + final int manageMenuBg = ta.getColor(2, Color.WHITE); ta.recycle(); + if (mManageButton != null) { + mManageButton.getBackground().setColorFilter(manageMenuBg, PorterDuff.Mode.SRC_IN); + } if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); 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 8e9fc1131e4a..a48be5ed5ad5 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 @@ -38,8 +38,10 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Outline; import android.graphics.PointF; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; @@ -1203,6 +1205,12 @@ public class BubbleStackView extends FrameLayout R.layout.bubble_manage_menu, this, false); mManageMenu.setVisibility(View.INVISIBLE); + final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ + com.android.internal.R.attr.materialColorSurfaceBright}); + final int menuBackgroundColor = ta.getColor(0, Color.WHITE); + ta.recycle(); + mManageMenu.getBackground().setColorFilter(menuBackgroundColor, PorterDuff.Mode.SRC_IN); + PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig); mManageMenu.setOutlineProvider(new ViewOutlineProvider() { 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 4980e49a6fc3..bc0b71c97346 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 @@ -47,6 +47,7 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; @@ -264,8 +265,13 @@ public abstract class WMShellModule { static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { - return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel); + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor) { + return new FreeformTaskTransitionHandler(shellInit, transitions, context, + windowDecorViewModel, displayController, mainExecutor, animExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index b9d2be280efb..546ce62fda59 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -508,5 +508,15 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll ); return result[0]; } + + @Override + public void stashDesktopApps(int displayId) throws RemoteException { + // Stashing of desktop apps not needed. Apps always launch on desktop + } + + @Override + public void setTaskListener(IDesktopTaskListener listener) throws RemoteException { + // TODO(b/261234402): move visibility from sysui state to listener + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 3ab175d3b68a..402bb96dc0c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -43,6 +43,7 @@ class DesktopModeTaskRepository { */ val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), + var stashed: Boolean = false ) // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). @@ -312,6 +313,33 @@ class DesktopModeTaskRepository { } /** + * Update stashed status on display with id [displayId] + */ + fun setStashed(displayId: Int, stashed: Boolean) { + val data = displayData.getOrCreate(displayId) + val oldValue = data.stashed + data.stashed = stashed + if (oldValue != stashed) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: mark stashed=%b displayId=%d", + stashed, + displayId + ) + visibleTasksListeners.forEach { (listener, executor) -> + executor.execute { listener.onStashedChanged(displayId, stashed) } + } + } + } + + /** + * Check if display with id [displayId] has desktop tasks stashed + */ + fun isStashed(displayId: Int): Boolean { + return displayData[displayId]?.stashed ?: false + } + + /** * Defines interface for classes that can listen to changes for active tasks in desktop mode. */ interface ActiveTasksListener { @@ -331,5 +359,11 @@ class DesktopModeTaskRepository { */ @JvmDefault fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} + + /** + * Called when the desktop stashed status changes. + */ + @JvmDefault + fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 91bb155d9d01..66f486dbca58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -46,6 +46,7 @@ import com.android.wm.shell.common.ExecutorUtils import com.android.wm.shell.common.ExternalInterfaceBinder import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread @@ -118,6 +119,18 @@ class DesktopTasksController( } } + /** + * Stash desktop tasks on display with id [displayId]. + * + * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps + * launched in this state will be added to the desktop. Existing desktop tasks will be brought + * back to front during the launch. + */ + fun stashDesktopApps(displayId: Int) { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") + desktopModeTaskRepository.setStashed(displayId, true) + } + /** Get number of tasks that are marked as visible */ fun getVisibleTaskCount(displayId: Int): Int { return desktopModeTaskRepository.getVisibleTaskCount(displayId) @@ -397,6 +410,11 @@ class DesktopTasksController( transition: IBinder, request: TransitionRequestInfo ): WindowContainerTransaction? { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest request=%s", + request + ) // Check if we should skip handling this transition val shouldHandleRequest = when { @@ -418,43 +436,63 @@ class DesktopTasksController( } val task: RunningTaskInfo = request.triggerTask - val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) - // Check if we should switch a fullscreen task to freeform - if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { - // If there are any visible desktop tasks, switch the task to freeform - if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + return when { + // If display has tasks stashed, handle as stashed launch + desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task) + // Check if fullscreen task should be updated + task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task) + // Check if freeform task should be updated + task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task) + else -> null + } + } + + private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch fullscreen task to freeform on transition" + - " taskId=%d", + "DesktopTasksController: switch freeform task to fullscreen oon transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToFullscreenChanges(wct, task.token) } } + return null + } - // CHeck if we should switch a freeform task to fullscreen - if (task.windowingMode == WINDOWING_MODE_FREEFORM) { - // If no visible desktop tasks, switch this task to freeform as the transition came - // outside of this controller - if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch freeform task to fullscreen oon transition" + - " taskId=%d", + "DesktopTasksController: switch fullscreen task to freeform on transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToFullscreenChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToDesktopChanges(wct, task.token) } } return null } + private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: launch apps with stashed on transition taskId=%d", + task.taskId + ) + val wct = WindowContainerTransaction() + bringDesktopAppsToFront(task.displayId, wct) + addMoveToDesktopChanges(wct, task.token) + desktopModeTaskRepository.setStashed(task.displayId, false) + return wct + } + private fun addMoveToDesktopChanges( wct: WindowContainerTransaction, token: WindowContainerToken @@ -658,8 +696,46 @@ class DesktopTasksController( @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { + + private lateinit var remoteListener: + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener> + + private val listener: VisibleTasksListener = object : VisibleTasksListener { + override fun onVisibilityChanged(displayId: Int, visible: Boolean) { + // TODO(b/261234402): move visibility from sysui state to listener + remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) } + } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onStashedChanged stashed=%b display=%d", + stashed, + displayId + ) + remoteListener.call { l -> l.onStashedChanged(displayId, stashed) } + } + } + + init { + remoteListener = + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>( + controller, + { c -> + c.desktopModeTaskRepository.addVisibleTasksListener( + listener, + c.mainExecutor + ) + }, + { c -> + c.desktopModeTaskRepository.removeVisibleTasksListener(listener) + } + ) + } + /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { + remoteListener.unregister() controller = null } @@ -670,6 +746,13 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId) } } + override fun stashDesktopApps(displayId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "stashDesktopApps" + ) { c -> c.stashDesktopApps(displayId) } + } + override fun getVisibleTaskCount(displayId: Int): Int { val result = IntArray(1) ExecutorUtils.executeRemoteCallWithTaskPermission( @@ -680,6 +763,18 @@ class DesktopTasksController( ) return result[0] } + + override fun setTaskListener(listener: IDesktopTaskListener?) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: set task listener=%s", + listener ?: "null" + ) + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "setTaskListener" + ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() } + } } companion object { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 899d67267e69..e7d353f56e30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -16,6 +16,8 @@ package com.android.wm.shell.desktopmode; +import com.android.wm.shell.desktopmode.IDesktopTaskListener; + /** * Interface that is exposed to remote callers to manipulate desktop mode features. */ @@ -24,6 +26,12 @@ interface IDesktopMode { /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId); + /** Stash apps on the desktop to allow launching another app from home screen */ + void stashDesktopApps(int displayId); + /** Get count of visible desktop tasks on the given display */ int getVisibleTaskCount(int displayId); + + /** Set listener that will receive callbacks about updates to desktop tasks */ + oneway void setTaskListener(IDesktopTaskListener listener); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl new file mode 100644 index 000000000000..39128a863ec9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.desktopmode; + +/** + * Allows external processes to register a listener in WMShell to get updates about desktop task + * state. + */ +interface IDesktopTaskListener { + + /** Desktop task visibility has change. Visible if at least 1 task is visible. */ + oneway void onVisibilityChanged(int displayId, boolean visible); + + /** Desktop task stashed status has changed. */ + oneway void onStashedChanged(int displayId, boolean stashed); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 04fc79acadbd..55e34fe3d836 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -19,9 +19,15 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; +import android.util.ArrayMap; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -31,6 +37,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -39,23 +47,37 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring - * transitions. + * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and + * restoring transitions. */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { - + private static final int CLOSE_ANIM_DURATION = 400; + private final Context mContext; private final Transitions mTransitions; private final WindowDecorViewModel mWindowDecorViewModel; + private final DisplayController mDisplayController; + private final ShellExecutor mMainExecutor; + private final ShellExecutor mAnimExecutor; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); + public FreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + ShellExecutor mainExecutor, + ShellExecutor animExecutor) { mTransitions = transitions; + mContext = context; mWindowDecorViewModel = windowDecorViewModel; + mDisplayController = displayController; + mMainExecutor = mainExecutor; + mAnimExecutor = animExecutor; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } @@ -103,6 +125,14 @@ public class FreeformTaskTransitionHandler @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; + final ArrayList<Animator> animations = new ArrayList<>(); + final Runnable onAnimFinish = () -> { + if (!animations.isEmpty()) return; + mMainExecutor.execute(() -> { + mAnimations.remove(transition); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + }); + }; for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -121,21 +151,45 @@ public class FreeformTaskTransitionHandler case WindowManager.TRANSIT_TO_BACK: transitionHandled |= startMinimizeTransition(transition); break; + case WindowManager.TRANSIT_CLOSE: + if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + transitionHandled |= startCloseTransition(transition, change, + finishT, animations, onAnimFinish); + } + break; } } - - mPendingTransitionTokens.remove(transition); - if (!transitionHandled) { return false; } - + mAnimations.put(transition, animations); + // startT must be applied before animations start. startT.apply(); - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.start(); + } + }); + // Run this here in case no animators are created. + onAnimFinish.run(); + mPendingTransitionTokens.remove(transition); return true; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ArrayList<Animator> animations = mAnimations.get(mergeTarget); + if (animations == null) return; + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.end(); + } + }); + + } + private boolean startChangeTransition( IBinder transition, int type, @@ -165,6 +219,36 @@ public class FreeformTaskTransitionHandler return mPendingTransitionTokens.contains(transition); } + private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change, + SurfaceControl.Transaction finishT, ArrayList<Animator> animations, + Runnable onAnimFinish) { + if (!mPendingTransitionTokens.contains(transition)) return false; + int screenHeight = mDisplayController + .getDisplayLayout(change.getTaskInfo().displayId).height(); + ValueAnimator animator = new ValueAnimator(); + animator.setDuration(CLOSE_ANIM_DURATION) + .setFloatValues(0f, 1f); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl sc = change.getLeash(); + finishT.hide(sc); + Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration + .getBounds()); + animator.addUpdateListener(animation -> { + t.setPosition(sc, startBounds.left, + startBounds.top + (animation.getAnimatedFraction() * screenHeight)); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animations.remove(animator); + onAnimFinish.run(); + } + }); + animations.add(animator); + return true; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index bbfeb90704db..57cc28d1dde5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -328,6 +328,8 @@ public class PipAnimationController { private PipSurfaceTransactionHelper mSurfaceTransactionHelper; private @TransitionDirection int mTransitionDirection; protected PipContentOverlay mContentOverlay; + // Flag to avoid double-end + private boolean mHasRequestedEnd; private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, @@ -357,6 +359,7 @@ public class PipAnimationController { @Override public void onAnimationUpdate(ValueAnimator animation) { + if (mHasRequestedEnd) return; applySurfaceControlTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction(), animation.getAnimatedFraction()); @@ -364,6 +367,8 @@ public class PipAnimationController { @Override public void onAnimationEnd(Animator animation) { + if (mHasRequestedEnd) return; + mHasRequestedEnd = true; mCurrentValue = mEndValue; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 9fa57cacb11f..c701b9581ca2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -206,6 +206,7 @@ public abstract class PipContentOverlay { tx.show(mLeash); tx.setLayer(mLeash, Integer.MAX_VALUE); tx.setBuffer(mLeash, mBitmap.getHardwareBuffer()); + tx.setAlpha(mLeash, 0f); tx.reparent(mLeash, parentLeash); tx.apply(); } 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 d07641892552..613791ccc062 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 @@ -31,12 +31,19 @@ import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU; import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.Path; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; import android.os.Handler; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; @@ -91,6 +98,8 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L private final ImageView mArrowLeft; private final TvWindowMenuActionButton mA11yDoneButton; + private final int mArrowElevation; + private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU; private final Rect mCurrentPipBounds = new Rect(); private int mCurrentPipGravity; @@ -131,21 +140,70 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left); mA11yDoneButton = findViewById(R.id.tv_pip_menu_done_button); - mResizeAnimationDuration = context.getResources().getInteger( - R.integer.config_pipResizeAnimationDuration); - mPipMenuFadeAnimationDuration = context.getResources() - .getInteger(R.integer.tv_window_menu_fade_animation_duration); + final Resources res = context.getResources(); + mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); + mPipMenuFadeAnimationDuration = + res.getInteger(R.integer.tv_window_menu_fade_animation_duration); + mPipMenuOuterSpace = res.getDimensionPixelSize(R.dimen.pip_menu_outer_space); + mPipMenuBorderWidth = res.getDimensionPixelSize(R.dimen.pip_menu_border_width); + mArrowElevation = res.getDimensionPixelSize(R.dimen.pip_menu_arrow_elevation); - mPipMenuOuterSpace = context.getResources() - .getDimensionPixelSize(R.dimen.pip_menu_outer_space); - mPipMenuBorderWidth = context.getResources() - .getDimensionPixelSize(R.dimen.pip_menu_border_width); + initMoveArrows(); mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, this); mEduTextContainer = (ViewGroup) findViewById(R.id.tv_pip_menu_edu_text_container); mEduTextContainer.addView(mEduTextDrawer); } + private void initMoveArrows() { + final int arrowSize = + mContext.getResources().getDimensionPixelSize(R.dimen.pip_menu_arrow_size); + final Path arrowPath = createArrowPath(arrowSize); + + final ShapeDrawable arrowDrawable = new ShapeDrawable(); + arrowDrawable.setShape(new PathShape(arrowPath, arrowSize, arrowSize)); + arrowDrawable.setTint(mContext.getResources().getColor(R.color.tv_pip_menu_arrow_color)); + + final ViewOutlineProvider arrowOutlineProvider = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setPath(createArrowPath(view.getMeasuredHeight())); + } + }; + + initArrow(mArrowRight, arrowOutlineProvider, arrowDrawable, 0); + initArrow(mArrowDown, arrowOutlineProvider, arrowDrawable, 90); + initArrow(mArrowLeft, arrowOutlineProvider, arrowDrawable, 180); + initArrow(mArrowUp, arrowOutlineProvider, arrowDrawable, 270); + } + + /** + * Creates a Path for a movement arrow in the MODE_MOVE_MENU. The resulting Path is a simple + * right-pointing triangle with its tip in the center of a size x size square: + * _ _ _ _ _ + * |* | + * |* * | + * |* * | + * |* * | + * |* _ _ _ _| + * + */ + private Path createArrowPath(int size) { + final Path triangle = new Path(); + triangle.lineTo(0, size); + triangle.lineTo(size / 2, size / 2); + triangle.close(); + return triangle; + } + + private void initArrow(View v, ViewOutlineProvider arrowOutlineProvider, Drawable arrowDrawable, + int rotation) { + v.setOutlineProvider(arrowOutlineProvider); + v.setBackground(arrowDrawable); + v.setRotation(rotation); + v.setElevation(mArrowElevation); + } + void onPipTransitionToTargetBoundsStarted(Rect targetBounds) { if (targetBounds == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 38c420a15231..14ea86a8c0e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -20,8 +20,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; +import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; @@ -86,6 +87,7 @@ class SplitScreenTransitions { mStageCoordinator = stageCoordinator; } + /** Play animation for enter transition or dismiss transition. */ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -116,6 +118,7 @@ class SplitScreenTransitions { playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot); } + /** Internal funcation of playAnimation. */ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { @@ -126,8 +129,8 @@ class SplitScreenTransitions { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); + final int rootIdx = TransitionUtil.rootIndexFor(change, info); if (mode == TRANSIT_CHANGE) { - final int rootIdx = TransitionUtil.rootIndexFor(change, info); if (change.getParent() != null) { // This is probably reparented, so we want the parent to be immediately visible final TransitionInfo.Change parentChange = info.getChange(change.getParent()); @@ -155,7 +158,7 @@ class SplitScreenTransitions { mFinishTransaction.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); mFinishTransaction.setCrop(leash, null); - } else if (isEnter && isTopRoot) { + } else if (isTopRoot) { // Ensure top root is visible at start. t.setAlpha(leash, 1.f); t.show(leash); @@ -168,40 +171,56 @@ class SplitScreenTransitions { t.setLayer(leash, Integer.MAX_VALUE); t.show(leash); } - // These container changes we don't want to animate them. - // We should only animate stage root, divider and child tasks are not under stage root. - if (isTopRoot || isMainChild || isSideChild || change.getTaskInfo() == null) { + + // We want to use child tasks to animate so ignore split root container and non task + // except divider change. + if (isTopRoot || isMainRoot || isSideRoot + || (change.getTaskInfo() == null && !isDivider)) { continue; } - if (isEnter && mPendingEnter.mResizeAnim) { // We will run animation in next transition so skip anim here continue; - } else if (isEnter && isMainRoot) { - // Main stage already on top so skip fade in animation to reduce flicker. + } else if (isPendingDismiss(transition) + && mPendingDismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { + // TODO(b/280020345): need to refine animation for this but just skip anim now. continue; } + // Because cross fade might be looked more flicker during animation + // (surface become black in middle of animation), we only do fade-out + // and show opening surface directly. boolean isOpening = TransitionUtil.isOpeningType(info.getType()); - if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { - // fade in - startFadeAnimation(leash, true /* show */); - } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { + if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { // fade out - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Dismissing via snap-to-top/bottom means that the dismissed task is already - // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 - // and don't animate it so it doesn't pop-in when reparented. - t.setAlpha(leash, 0.f); + if (change.getSnapshot() != null) { + // This case is happened if task is going to reparent to TDA, the origin leash + // doesn't rendor so we use snapshot to replace it animating. + t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash()); + // Use origin leash layer. + t.setLayer(change.getSnapshot(), info.getChanges().size() - i); + t.setPosition(change.getSnapshot(), change.getStartAbsBounds().left, + change.getStartAbsBounds().top); + t.show(change.getSnapshot()); + startFadeAnimation(change.getSnapshot(), false /* show */); } else { startFadeAnimation(leash, false /* show */); } + } else if (mode == TRANSIT_CHANGE && change.getSnapshot() != null) { + t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash()); + // Ensure snapshot it on the top of all transition surfaces + t.setLayer(change.getSnapshot(), info.getChanges().size() + 1); + t.setPosition(change.getSnapshot(), change.getStartAbsBounds().left, + change.getStartAbsBounds().top); + t.show(change.getSnapshot()); + startFadeAnimation(change.getSnapshot(), false /* show */); } } t.apply(); onFinish(null /* wct */, null /* wctCB */); } + /** Play animation for resize transition. */ void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -437,6 +456,7 @@ class SplitScreenTransitions { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); final ValueAnimator va = ValueAnimator.ofFloat(start, end); va.setDuration(FADE_DURATION); + va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT); va.addUpdateListener(animation -> { float fraction = animation.getAnimatedFraction(); transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 087e3a2384d6..bf20567834e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -582,7 +582,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); @@ -604,7 +603,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); @@ -625,7 +623,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); addActivityOptions(options1, mSideStage); @@ -691,7 +688,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, false /* reparent */); } - prepareEvictChildTasksIfSplitActive(wct); mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); @@ -1075,24 +1071,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct); - prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct); + mMainStage.evictNonOpeningChildren(apps, evictWct); + mSideStage.evictNonOpeningChildren(apps, evictWct); mSyncQueue.queue(evictWct); } - - /** - * Collects all the current child tasks of a specific split and prepares transaction to evict - * them to display. - */ - void prepareEvictChildTasks(@SplitPosition int position, WindowContainerTransaction wct) { - if (position == mSideStagePosition) { - mSideStage.evictAllChildren(wct); - } else { - mMainStage.evictAllChildren(wct); - } - } - void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps, WindowContainerTransaction wct) { if (position == mSideStagePosition) { @@ -1107,13 +1090,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.evictInvisibleChildren(wct); } - void prepareEvictChildTasksIfSplitActive(WindowContainerTransaction wct) { - if (mMainStage.isActive()) { - mMainStage.evictAllChildren(wct); - mSideStage.evictAllChildren(wct); - } - } - Bundle resolveStartStage(@StageType int stage, @SplitPosition int position, @Nullable Bundle options, @Nullable WindowContainerTransaction wct) { switch (stage) { @@ -1500,10 +1476,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(taskInfo.taskId, resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct)); } - // If running background, we need to reparent current top visible task to another stage - // and evict all tasks current under its. + // If running background, we need to reparent current top visible task to main stage. if (!isSplitScreenVisible()) { - // Recreate so we need to reset position rather than keep position of background split. mMainStage.reparentTopTask(wct); prepareSplitLayout(wct); } @@ -2061,18 +2035,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Reset this flag every time onLayoutSizeChanged. mShowDecorImmediately = false; - if (!ENABLE_SHELL_TRANSITIONS) { - // Only need screenshot for legacy case because shell transition should screenshot - // itself during transition. - final SurfaceControl.Transaction startT = mTransactionPool.acquire(); - mMainStage.screenshotIfNeeded(startT); - mSideStage.screenshotIfNeeded(startT); - mTransactionPool.release(startT); - } - final WindowContainerTransaction wct = new WindowContainerTransaction(); boolean sizeChanged = updateWindowBounds(layout, wct); - if (!sizeChanged) return; + if (!sizeChanged) { + // We still need to resize on decor for ensure all current status clear. + final SurfaceControl.Transaction t = mTransactionPool.acquire(); + mMainStage.onResized(t); + mSideStage.onResized(t); + mTransactionPool.release(t); + return; + } sendOnBoundsChanged(); if (ENABLE_SHELL_TRANSITIONS) { @@ -2080,6 +2052,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) -> mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish")); } else { + // Only need screenshot for legacy case because shell transition should screenshot + // itself during transition. + final SurfaceControl.Transaction startT = mTransactionPool.acquire(); + mMainStage.screenshotIfNeeded(startT); + mSideStage.screenshotIfNeeded(startT); + mTransactionPool.release(startT); + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { updateSurfaceBounds(layout, t, false /* applyResizingOffset */); @@ -2337,7 +2316,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), null /* consumedCallback */, null /* finishedCallback */, - 0 /* extraTransitType */, !mIsDropEntering); + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering); } } return out; @@ -2604,7 +2583,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { if (mainChild == null && sideChild == null) { Log.w(TAG, "Launched a task in split, but didn't receive any task in transition."); - mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */); + mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT) + -> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct)); return true; } } else { @@ -2614,10 +2594,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); mSplitTransitions.mPendingEnter.cancel( - (cancelWct, cancelT) -> { - mSideStage.removeAllTasks(cancelWct, dismissTop == STAGE_TYPE_SIDE); - mMainStage.deactivate(cancelWct, dismissTop == STAGE_TYPE_MAIN); - }); + (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); return true; } } @@ -2626,14 +2603,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // transitions locally, but remotes (like Launcher) may get confused if they were // depending on listener callbacks. This can happen because task-organizer callbacks // aren't serialized with transition callbacks. + // This usually occurred on app use trampoline launch new task and finish itself. // TODO(b/184679596): Find a way to either include task-org information in // the transition, or synchronize task-org callbacks. - if (mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId)) { + final boolean mainNotContainOpenTask = + mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId); + final boolean sideNotContainOpenTask = + sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId); + if (mainNotContainOpenTask) { Log.w(TAG, "Expected onTaskAppeared on " + mMainStage + " to have been called with " + mainChild.getTaskInfo().taskId + " before startAnimation()."); } - if (sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId)) { + if (sideNotContainOpenTask) { Log.w(TAG, "Expected onTaskAppeared on " + mSideStage + " to have been called with " + sideChild.getTaskInfo().taskId + " before startAnimation()."); @@ -2642,10 +2624,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final TransitionInfo.Change finalSideChild = sideChild; enterTransition.setFinishedCallback((callbackWct, callbackT) -> { if (finalMainChild != null) { - mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId); + if (!mainNotContainOpenTask) { + mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId); + } else { + mMainStage.evictInvisibleChildren(callbackWct); + } } if (finalSideChild != null) { - mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId); + if (!sideNotContainOpenTask) { + mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId); + } else { + mSideStage.evictInvisibleChildren(callbackWct); + } } if (enterTransition.mResizeAnim) { mShowDecorImmediately = true; @@ -2904,9 +2894,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitScreenVisible()) { mIsDropEntering = true; } - if (!isSplitScreenVisible()) { + if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { // If split running background, exit split first. - // TODO(b/280392203) : skip doing this on shell transition once this bug is fixed. + // Skip this on shell transition due to we could evict existing tasks on transition + // finished. exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); } mLogger.enterRequestedByDrag(position, dragSessionId); @@ -2916,9 +2907,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * Sets info to be logged when splitscreen is next entered. */ public void onRequestToSplit(InstanceId sessionId, int enterReason) { - if (!isSplitScreenVisible()) { + if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { // If split running background, exit split first. - // TODO(b/280392203) : skip doing this on shell transition once this bug is fixed. + // Skip this on shell transition due to we could evict existing tasks on transition + // finished. exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); } mLogger.enterRequested(sessionId, enterReason); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java index 6d37e58c59ad..143b42a1ec96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java @@ -24,9 +24,7 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -178,7 +176,14 @@ public class TransitionUtil { } // Put all the OPEN/SHOW on top - if (TransitionUtil.isOpeningType(mode)) { + if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + // Wallpaper is always at the bottom, opening wallpaper on top of closing one. + if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) { + t.setLayer(leash, -zSplitLine + info.getChanges().size() - layer); + } else { + t.setLayer(leash, -zSplitLine - layer); + } + } else if (TransitionUtil.isOpeningType(mode)) { if (isOpening) { t.setLayer(leash, zSplitLine + info.getChanges().size() - layer); if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { @@ -312,6 +317,7 @@ public class TransitionUtil { target.setWillShowImeOnTarget( (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0); target.setRotationChange(change.getEndRotation() - change.getStartRotation()); + target.backgroundColor = change.getBackgroundColor(); return target; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 39fb7936747e..2267c750a31b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -268,7 +268,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return false; } case MotionEvent.ACTION_MOVE: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; @@ -276,7 +279,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); final boolean wasDragging = mIsDragging; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9fd57d7e1201..15abbf298b24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -385,6 +385,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_MOVE: { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, decoration.mTaskSurface, e.getRawY(dragPointerIdx))); @@ -395,6 +398,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); // Position of the task is calculated by subtracting the raw location of the // motion event (the location of the motion relative to the display) by the diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index 65b5a7a17afe..58644b23ce12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -73,8 +73,11 @@ class DragDetector { return mResultOfDownAction; } case ACTION_MOVE: { + if (ev.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = ev.getPointerId(0); + } + final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); if (!mIsDragEvent) { - int dragPointerIndex = ev.findPointerIndex(mDragPointerId); float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; // Touches generate noisy moves, so only once the move is past the touch diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java deleted file mode 100644 index d62e6601723a..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.back; - -import static org.junit.Assert.assertEquals; - -import android.window.BackEvent; -import android.window.BackMotionEvent; - -import org.junit.Before; -import org.junit.Test; - -public class TouchTrackerTest { - private static final float FAKE_THRESHOLD = 400; - private static final float INITIAL_X_LEFT_EDGE = 5; - private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE; - private TouchTracker mTouchTracker; - - @Before - public void setUp() throws Exception { - mTouchTracker = new TouchTracker(); - mTouchTracker.setProgressThreshold(FAKE_THRESHOLD); - } - - @Test - public void generatesProgress_onStart() { - mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); - BackMotionEvent event = mTouchTracker.createStartEvent(null); - assertEquals(event.getProgress(), 0f, 0f); - } - - @Test - public void generatesProgress_leftEdge() { - mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); - float touchX = 10; - float velocityX = 0; - float velocityY = 0; - - // Pre-commit - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); - - // Post-commit - touchX += 100; - mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); - - // Cancel - touchX -= 10; - mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Cancel more - touchX -= 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Restart - touchX += 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Restarted, but pre-commit - float restartX = touchX; - touchX += 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f); - - // Restarted, post-commit - touchX += 10; - mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); - } - - @Test - public void generatesProgress_rightEdge() { - mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT); - float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge - float velocityX = 0f; - float velocityY = 0f; - - // Pre-commit - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); - - // Post-commit - touchX -= 100; - mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); - - // Cancel - touchX += 10; - mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Cancel more - touchX += 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Restart - touchX -= 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), 0, 0f); - - // Restarted, but pre-commit - float restartX = touchX; - touchX -= 10; - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f); - - // Restarted, post-commit - touchX -= 10; - mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0, velocityX, velocityY); - assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); - } - - private float getProgress() { - return mTouchTracker.createProgressEvent().getProgress(); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt new file mode 100644 index 000000000000..9088e8997e79 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt @@ -0,0 +1,181 @@ +/* + * 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.back + +import android.util.MathUtils +import android.window.BackEvent +import org.junit.Assert.assertEquals +import org.junit.Test + +class TouchTrackerTest { + private fun linearTouchTracker(): TouchTracker = TouchTracker().apply { + setProgressThresholds(MAX_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR) + } + + private fun nonLinearTouchTracker(): TouchTracker = TouchTracker().apply { + setProgressThresholds(LINEAR_DISTANCE, MAX_DISTANCE, NON_LINEAR_FACTOR) + } + + private fun TouchTracker.assertProgress(expected: Float) { + val actualProgress = createProgressEvent().progress + assertEquals(expected, actualProgress, /* delta = */ 0f) + } + + @Test + fun generatesProgress_onStart() { + val linearTracker = linearTouchTracker() + linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) + val event = linearTracker.createStartEvent(null) + assertEquals(0f, event.progress, 0f) + } + + @Test + fun generatesProgress_leftEdge() { + val linearTracker = linearTouchTracker() + linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) + var touchX = 10f + val velocityX = 0f + val velocityY = 0f + + // Pre-commit + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) + + // Post-commit + touchX += 100f + linearTracker.setTriggerBack(true) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) + + // Cancel + touchX -= 10f + linearTracker.setTriggerBack(false) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Cancel more + touchX -= 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Restart + touchX += 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Restarted, but pre-commit + val restartX = touchX + touchX += 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((touchX - restartX) / MAX_DISTANCE) + + // Restarted, post-commit + touchX += 10f + linearTracker.setTriggerBack(true) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE) + } + + @Test + fun generatesProgress_rightEdge() { + val linearTracker = linearTouchTracker() + linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT) + var touchX = INITIAL_X_RIGHT_EDGE - 10 // Fake right edge + val velocityX = 0f + val velocityY = 0f + val target = MAX_DISTANCE + + // Pre-commit + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) + + // Post-commit + touchX -= 100f + linearTracker.setTriggerBack(true) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) + + // Cancel + touchX += 10f + linearTracker.setTriggerBack(false) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Cancel more + touchX += 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Restart + touchX -= 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress(0f) + + // Restarted, but pre-commit + val restartX = touchX + touchX -= 10f + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((restartX - touchX) / target) + + // Restarted, post-commit + touchX -= 10f + linearTracker.setTriggerBack(true) + linearTracker.update(touchX, 0f, velocityX, velocityY) + linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / target) + } + + @Test + fun generatesNonLinearProgress_leftEdge() { + val nonLinearTracker = nonLinearTouchTracker() + nonLinearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT) + var touchX = 10f + val velocityX = 0f + val velocityY = 0f + val linearTarget = LINEAR_DISTANCE + (MAX_DISTANCE - LINEAR_DISTANCE) * NON_LINEAR_FACTOR + + // Pre-commit: linear progress + nonLinearTracker.update(touchX, 0f, velocityX, velocityY) + nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) + + // Post-commit: still linear progress + touchX += 100f + nonLinearTracker.setTriggerBack(true) + nonLinearTracker.update(touchX, 0f, velocityX, velocityY) + nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) + + // still linear progress + touchX = INITIAL_X_LEFT_EDGE + LINEAR_DISTANCE + nonLinearTracker.update(touchX, 0f, velocityX, velocityY) + nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / linearTarget) + + // non linear progress + touchX += 10 + nonLinearTracker.update(touchX, 0f, velocityX, velocityY) + val nonLinearTouch = (touchX - INITIAL_X_LEFT_EDGE) - LINEAR_DISTANCE + val nonLinearProgress = nonLinearTouch / NON_LINEAR_DISTANCE + val nonLinearTarget = MathUtils.lerp(linearTarget, MAX_DISTANCE, nonLinearProgress) + nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget) + } + + companion object { + private const val MAX_DISTANCE = 500f + private const val LINEAR_DISTANCE = 400f + private const val NON_LINEAR_DISTANCE = MAX_DISTANCE - LINEAR_DISTANCE + private const val NON_LINEAR_FACTOR = 0.2f + private const val INITIAL_X_LEFT_EDGE = 5f + private const val INITIAL_X_RIGHT_EDGE = MAX_DISTANCE - INITIAL_X_LEFT_EDGE + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 3bc2f0e8674e..17c0463309d7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -313,6 +313,65 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(tasks.first()).isEqualTo(6) } + @Test + fun setStashed_stateIsUpdatedForTheDisplay() { + repo.setStashed(DEFAULT_DISPLAY, true) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse() + + repo.setStashed(DEFAULT_DISPLAY, false) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test + fun setStashed_notifyListener() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun setStashed_secondCallDoesNotNotify() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun setStashed_tracksPerDisplay() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isFalse() + + repo.setStashed(SECOND_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + } + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 @@ -332,6 +391,12 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { var visibleChangesOnDefaultDisplay = 0 var visibleChangesOnSecondaryDisplay = 0 + var stashedOnDefaultDisplay = false + var stashedOnSecondaryDisplay = false + + var stashedChangesOnDefaultDisplay = 0 + var stashedChangesOnSecondaryDisplay = 0 + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { when (displayId) { DEFAULT_DISPLAY -> { @@ -345,6 +410,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { else -> fail("Visible task listener received unexpected display id: $displayId") } } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + when (displayId) { + DEFAULT_DISPLAY -> { + stashedOnDefaultDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + SECOND_DISPLAY -> { + stashedOnSecondaryDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + else -> fail("Visible task listener received unexpected display id: $displayId") + } + } } companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 1335ebf105a6..dbd8a469c5d1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -451,6 +451,27 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + assertThat(result).isNotNull() + result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) + assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -501,6 +522,25 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val freeformTask = createFreeformTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + assertThat(result).isNotNull() + result?.assertReorderSequence(stashedFreeformTask, freeformTask) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -539,6 +579,14 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() } + @Test + fun stashDesktopApps_stateUpdates() { + controller.stashDesktopApps(DEFAULT_DISPLAY) + + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) |