diff options
Diffstat (limited to 'libs')
59 files changed, 1808 insertions, 436 deletions
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 54978bd4496d..71598938f42f 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -42,16 +42,19 @@ filegroup { filegroup { name: "wm_shell_util-sources", srcs: [ - "src/com/android/wm/shell/util/**/*.java", + "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/animation/PhysicsAnimator.kt", + "src/com/android/wm/shell/common/bubbles/*.kt", + "src/com/android/wm/shell/common/bubbles/*.java", + "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt", "src/com/android/wm/shell/common/split/SplitScreenConstants.java", - "src/com/android/wm/shell/sysui/ShellSharedConstants.java", "src/com/android/wm/shell/common/TransactionPool.java", - "src/com/android/wm/shell/common/bubbles/*.java", "src/com/android/wm/shell/common/TriangleShape.java", - "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", "src/com/android/wm/shell/pip/PipContentOverlay.java", "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java", - "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", + "src/com/android/wm/shell/sysui/ShellSharedConstants.java", + "src/com/android/wm/shell/util/**/*.java", ], path: "src", } diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 214125928892..2e3f60441b3a 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -416,7 +416,7 @@ <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> - <dimen name="freeform_resize_handle">30dp</dimen> + <dimen name="freeform_resize_handle">15dp</dimen> <dimen name="freeform_resize_corner">44dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 8635c56b7bc6..d902fd49ba60 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -93,62 +93,34 @@ <style name="RestartDialogTitleText"> <item name="android:textSize">24sp</item> <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:lineSpacingExtra">2sp</item> - <item name="android:textAppearance"> - @*android:style/TextAppearance.DeviceDefault.Headline - </item> - <item name="android:fontFamily"> - @*android:string/config_bodyFontFamilyMedium - </item> + <item name="android:lineSpacingExtra">8sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> </style> - <style name="RestartDialogBodyText"> + <style name="RestartDialogBodyStyle"> <item name="android:textSize">14sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + </style> + + <style name="RestartDialogBodyText" parent="RestartDialogBodyStyle"> <item name="android:letterSpacing">0.02</item> <item name="android:textColor">?android:attr/textColorSecondary</item> - <item name="android:lineSpacingExtra">2sp</item> - <item name="android:textAppearance"> - @*android:style/TextAppearance.DeviceDefault.Body2 - </item> - <item name="android:fontFamily"> - @*android:string/config_bodyFontFamily - </item> + <item name="android:lineSpacingExtra">6sp</item> </style> - <style name="RestartDialogCheckboxText"> - <item name="android:textSize">16sp</item> + <style name="RestartDialogCheckboxText" parent="RestartDialogBodyStyle"> <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:lineSpacingExtra">4sp</item> - <item name="android:textAppearance"> - @*android:style/TextAppearance.DeviceDefault.Headline - </item> - <item name="android:fontFamily"> - @*android:string/config_bodyFontFamilyMedium - </item> + <item name="android:lineSpacingExtra">6sp</item> </style> - <style name="RestartDialogDismissButton"> + <style name="RestartDialogDismissButton" parent="RestartDialogBodyStyle"> <item name="android:lineSpacingExtra">2sp</item> - <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:textAppearance"> - @*android:style/TextAppearance.DeviceDefault.Body2 - </item> - <item name="android:fontFamily"> - @*android:string/config_bodyFontFamily - </item> </style> - <style name="RestartDialogConfirmButton"> + <style name="RestartDialogConfirmButton" parent="RestartDialogBodyStyle"> <item name="android:lineSpacingExtra">2sp</item> - <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> - <item name="android:textAppearance"> - @*android:style/TextAppearance.DeviceDefault.Body2 - </item> - <item name="android:fontFamily"> - @*android:string/config_bodyFontFamily - </item> </style> <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat.Light"> 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 146774189490..c48f2fdaf42f 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 @@ -80,8 +80,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.window.ScreenCapture; -import android.window.ScreenCapture.ScreenCaptureListener; -import android.window.ScreenCapture.ScreenshotSync; +import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -1230,10 +1229,11 @@ public class BubbleController implements ConfigurationChangeListener, /** * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot - * can be access via the supplied {@link ScreenshotSync#get()} asynchronously. + * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()} + * asynchronously. */ public void getScreenshotExcludingBubble(int displayId, - Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener) { + SynchronousScreenCaptureListener screenCaptureListener) { try { ScreenCapture.CaptureArgs args = null; if (mStackView != null) { @@ -1248,7 +1248,7 @@ public class BubbleController implements ConfigurationChangeListener, } } - mWmService.captureDisplay(displayId, args, screenCaptureListener.first); + mWmService.captureDisplay(displayId, args, screenCaptureListener); } catch (RemoteException e) { Log.e(TAG, "Failed to capture screenshot"); } @@ -2219,15 +2219,15 @@ public class BubbleController implements ConfigurationChangeListener, @Override @Nullable - public ScreenshotSync getScreenshotExcludingBubble(int displayId) { - Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener = + public SynchronousScreenCaptureListener getScreenshotExcludingBubble(int displayId) { + SynchronousScreenCaptureListener screenCaptureListener = ScreenCapture.createSyncCaptureListener(); mMainExecutor.execute( () -> BubbleController.this.getScreenshotExcludingBubble(displayId, screenCaptureListener)); - return screenCaptureListener.second; + return screenCaptureListener; } @Override 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 68fea41e134e..9860b076264b 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 @@ -88,6 +88,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.bubbles.RelativeTouchListener; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -1179,6 +1181,7 @@ public class BubbleStackView extends FrameLayout removeView(mDismissView); } mDismissView = new DismissView(getContext()); + DismissViewUtils.setup(mDismissView); int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation); addView(mDismissView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 259f69296ac7..4d329dd5d446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -16,8 +16,6 @@ package com.android.wm.shell.bubbles; -import static android.window.ScreenCapture.ScreenshotSync; - import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; @@ -34,6 +32,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.util.Pair; import android.util.SparseArray; import android.window.ScreenCapture.ScreenshotHardwareBuffer; +import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -150,13 +149,14 @@ public interface Bubbles { boolean isAppBubbleTaskId(int taskId); /** - * @return a {@link ScreenshotSync} after performing a screenshot that may exclude the bubble - * layer, if one is present. The underlying {@link ScreenshotHardwareBuffer} can be access via - * {@link ScreenshotSync#get()} asynchronously and care should be taken to - * {@link HardwareBuffer#close()} the associated - * {@link ScreenshotHardwareBuffer#getHardwareBuffer()} when no longer required. +` * @return a {@link SynchronousScreenCaptureListener} after performing a screenshot that may + * exclude the bubble layer, if one is present. The underlying + * {@link ScreenshotHardwareBuffer} can be accessed via + * {@link SynchronousScreenCaptureListener#getBuffer()} asynchronously and care should be taken + * to {@link HardwareBuffer#close()} the associated + * {@link ScreenshotHardwareBuffer#getHardwareBuffer()} when no longer required.` */ - ScreenshotSync getScreenshotExcludingBubble(int displayId); + SynchronousScreenCaptureListener getScreenshotExcludingBubble(int displayId); /** * @return a bubble that matches the provided shortcutId, if one exists. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt new file mode 100644 index 000000000000..ed3624035757 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt @@ -0,0 +1,33 @@ +/* + * 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. + */ +@file:JvmName("DismissViewUtils") + +package com.android.wm.shell.bubbles + +import com.android.wm.shell.R +import com.android.wm.shell.common.bubbles.DismissView + +fun DismissView.setup() { + setup(DismissView.Config( + targetSizeResId = R.dimen.dismiss_circle_size, + iconSizeResId = R.dimen.dismiss_target_x_size, + bottomMarginResId = R.dimen.floating_dismiss_bottom_margin, + floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height, + floatingGradientColorResId = android.R.color.system_neutral1_900, + backgroundResId = R.drawable.dismiss_circle_background, + iconResId = R.drawable.pip_ic_close_white + )) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index ae1f43320c8b..72702e7c2b88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -79,7 +79,7 @@ public class DisplayChangeController { } /** Query all listeners for changes that should happen on display change. */ - public void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId, + void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId, int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) { for (OnDisplayChangingListener c : mDisplayChangeListener) { c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index f07ea751b044..8353900be0ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -29,6 +29,7 @@ import android.view.Display; import android.view.IDisplayWindowListener; import android.view.IWindowManager; import android.view.InsetsState; +import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -85,11 +86,6 @@ public class DisplayController { } } - /** Get the DisplayChangeController. */ - public DisplayChangeController getChangeController() { - return mChangeController; - } - /** * Gets a display by id from DisplayManager. */ @@ -195,6 +191,26 @@ public class DisplayController { } } + + /** Called when a display rotate requested. */ + public void onDisplayRotateRequested(WindowContainerTransaction wct, int displayId, + int fromRotation, int toRotation) { + synchronized (mDisplays) { + final DisplayRecord dr = mDisplays.get(displayId); + if (dr == null) { + Slog.w(TAG, "Skipping Display rotate on non-added display."); + return; + } + + if (dr.mDisplayLayout != null) { + dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation); + } + + mChangeController.dispatchOnDisplayChange( + wct, displayId, fromRotation, toRotation, null /* newDisplayAreaInfo */); + } + } + private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { synchronized (mDisplays) { final DisplayRecord dr = mDisplays.get(displayId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index 467e9c7a116b..1959eb03a6b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -300,9 +300,12 @@ public class DisplayLayout { return mAllowSeamlessRotationDespiteNavBarMoving; } - /** @return whether the navigation bar will change sides during rotation. */ + /** + * Returns {@code true} if the navigation bar will change sides during rotation and the display + * is not square. + */ public boolean navigationBarCanMove() { - return mNavigationBarCanMove; + return mNavigationBarCanMove && mWidth != mHeight; } /** @return the rotation that would make the physical display "upside down". */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java index e0c782d1675b..7c5bb211a4cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.common; +package com.android.wm.shell.common.bubbles; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; -import com.android.wm.shell.R; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; /** * Circular view with a semitransparent, circular background with an 'X' inside it. @@ -31,33 +32,44 @@ import com.android.wm.shell.R; * This is used by both Bubbles and PIP as the dismiss target. */ public class DismissCircleView extends FrameLayout { + @DrawableRes int mBackgroundResId; + @DimenRes int mIconSizeResId; private final ImageView mIconView = new ImageView(getContext()); public DismissCircleView(Context context) { super(context); - final Resources res = getResources(); - - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); - - mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white)); addView(mIconView); - - setViewSizes(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - final Resources res = getResources(); - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); + setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId)); + setViewSizes(); + } + + /** + * Sets up view with the provided resource ids. + * Decouples resource dependency in order to be used externally (e.g. Launcher) + * + * @param backgroundResId drawable resource id of the circle background + * @param iconResId drawable resource id of the icon for the dismiss view + * @param iconSizeResId dimen resource id of the icon size + */ + public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId, + @DimenRes int iconSizeResId) { + mBackgroundResId = backgroundResId; + mIconSizeResId = iconSizeResId; + + setBackground(ContextCompat.getDrawable(getContext(), backgroundResId)); + mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId)); setViewSizes(); } /** Retrieves the current dimensions for the icon and circle and applies them. */ private void setViewSizes() { - final Resources res = getResources(); - final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size); + final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId); mIconView.setLayoutParams( new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt index 67ecb915e098..d275a0be8e93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt @@ -14,41 +14,73 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.animation.ObjectAnimator import android.content.Context import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.util.IntProperty +import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.widget.FrameLayout +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW -import com.android.wm.shell.R import com.android.wm.shell.animation.PhysicsAnimator -import com.android.wm.shell.common.DismissCircleView -/* +/** * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation */ class DismissView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = + "The view isn't ready. Should be called after `setup`" + private val TAG = DismissView::class.simpleName + } var circle = DismissCircleView(context) var isShowing = false - var targetSizeResId: Int + var config: Config? = null private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) private val DISMISS_SCRIM_FADE_MS = 200L private var wm: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - private var gradientDrawable = createGradient() + private var gradientDrawable: GradientDrawable? = null private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = object : IntProperty<GradientDrawable>("alpha") { @@ -61,23 +93,41 @@ class DismissView(context: Context) : FrameLayout(context) { } init { - setLayoutParams(LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), - Gravity.BOTTOM)) - updatePadding() setClipToPadding(false) setClipChildren(false) setVisibility(View.INVISIBLE) + addView(circle) + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) setBackgroundDrawable(gradientDrawable) - targetSizeResId = R.dimen.dismiss_circle_size - val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) - addView(circle, LayoutParams(targetSize, targetSize, - Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) - // start with circle offscreen so it's animated up - circle.setTranslationY(resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height).toFloat()) + // Setup DismissCircleView + circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId) + val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId) + circle.layoutParams = LayoutParams(targetSize, targetSize, + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + // Initial position with circle offscreen so it's animated up + circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + .toFloat() } /** @@ -85,6 +135,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun show() { if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = true setVisibility(View.VISIBLE) val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, @@ -104,6 +155,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun hide() { if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = false val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0) @@ -124,18 +176,17 @@ class DismissView(context: Context) : FrameLayout(context) { } fun updateResources() { + val config = checkExists(config) ?: return updatePadding() - layoutParams.height = resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height) - - val targetSize = resources.getDimensionPixelSize(targetSizeResId) + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() } - private fun createGradient(): GradientDrawable { - val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900) + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) val alpha = 0.7f * 255 val gradientColorWithAlpha = Color.argb(alpha.toInt(), Color.red(gradientColor), @@ -150,10 +201,22 @@ class DismissView(context: Context) : FrameLayout(context) { } private fun updatePadding() { + val config = checkExists(config) ?: return val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() val navInset = insets.getInsetsIgnoringVisibility( WindowInsets.Type.navigationBars()) setPadding(0, 0, 0, navInset.bottom + - resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) + resources.getDimensionPixelSize(config.bottomMarginResId)) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. + * Used for convenient logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun <T>checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt index ea9d065d5f53..cc37bd3a4589 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.graphics.PointF import android.view.MotionEvent diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index ab8e7e63ef7f..f70d3aec9ec8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -63,8 +63,10 @@ import com.android.internal.policy.DockedDividerUtils; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.InteractionJankMonitorUtils; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -104,6 +106,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final Rect mWinBounds2 = new Rect(); private final SplitLayoutHandler mSplitLayoutHandler; private final SplitWindowManager mSplitWindowManager; + private final DisplayController mDisplayController; private final DisplayImeController mDisplayImeController; private final ImePositionProcessor mImePositionProcessor; private final ResizingEffectPolicy mSurfaceEffectPolicy; @@ -128,13 +131,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public SplitLayout(String windowName, Context context, Configuration configuration, SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, - DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer, - int parallaxType) { + DisplayController displayController, DisplayImeController displayImeController, + ShellTaskOrganizer taskOrganizer, int parallaxType) { mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; mRotation = configuration.windowConfiguration.getRotation(); mDensity = configuration.densityDpi; mSplitLayoutHandler = splitLayoutHandler; + mDisplayController = displayController; mDisplayImeController = displayImeController; mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration, parentContainerCallbacks); @@ -145,7 +149,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); resetDividerPosition(); mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide); @@ -314,7 +318,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = rotation; mDensity = density; mUiMode = uiMode; - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); updateDividerConfig(mContext); initDividerPosition(mTempRect); updateInvisibleRect(); @@ -324,7 +328,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange /** Rotate the layout to specific rotation and calculate new bounds. The stable insets value * should be calculated by display layout. */ - public void rotateTo(int newRotation, Rect stableInsets) { + public void rotateTo(int newRotation) { final int rotationDelta = (newRotation - mRotation + 4) % 4; final boolean changeOrient = (rotationDelta % 2) != 0; @@ -337,7 +341,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // We only need new bounds here, other configuration should be update later. mTempRect.set(mRootBounds); mRootBounds.set(tmpRect); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, stableInsets); + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); initDividerPosition(mTempRect); } @@ -548,10 +552,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } - private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds, - @Nullable Rect stableInsets) { + private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) { final boolean isLandscape = isLandscape(rootBounds); - final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context); + final Rect insets = getDisplayStableInsets(context); // Make split axis insets value same as the larger one to avoid bounds1 and bounds2 // have difference for avoiding size-compat mode when switching unresizable apps in @@ -634,7 +637,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, SurfaceControl leash2, Consumer<Rect> finishCallback) { final boolean isLandscape = isLandscape(); - final Rect insets = getDisplayInsets(mContext); + final Rect insets = getDisplayStableInsets(mContext); insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top, isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom); @@ -705,13 +708,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return animator; } - private static Rect getDisplayInsets(Context context) { - return context.getSystemService(WindowManager.class) - .getMaximumWindowMetrics() - .getWindowInsets() - .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars() - | WindowInsets.Type.displayCutout()) - .toRect(); + private Rect getDisplayStableInsets(Context context) { + final DisplayLayout displayLayout = + mDisplayController.getDisplayLayout(context.getDisplayId()); + return displayLayout != null + ? displayLayout.stableInsets() + : context.getSystemService(WindowManager.class) + .getMaximumWindowMetrics() + .getWindowInsets() + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars() + | WindowInsets.Type.displayCutout()) + .toRect(); } private static boolean isLandscape(Rect bounds) { @@ -784,7 +791,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private int getSmallestWidthDp(Rect bounds) { mTempRect.set(bounds); - mTempRect.inset(getDisplayInsets(mContext)); + mTempRect.inset(getDisplayStableInsets(mContext)); final int minWidth = Math.min(mTempRect.width(), mTempRect.height()); final float density = mContext.getResources().getDisplayMetrics().density; return (int) (minWidth / density); 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 70e9079dc572..f663d4d758f7 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 @@ -57,6 +57,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; +import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; @@ -546,10 +547,11 @@ public abstract class WMShellModule { KeyguardTransitionHandler keyguardTransitionHandler, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, + Optional<UnfoldTransitionHandler> unfoldHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler, - desktopModeController, desktopTasksController); + desktopModeController, desktopTasksController, unfoldHandler); } @WMSingleton @@ -690,6 +692,7 @@ public abstract class WMShellModule { Transitions transitions, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, + ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, LaunchAdjacentController launchAdjacentController, @ShellMainThread ShellExecutor mainExecutor @@ -697,7 +700,8 @@ public abstract class WMShellModule { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, - desktopModeTaskRepository, launchAdjacentController, mainExecutor); + toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, + launchAdjacentController, mainExecutor); } @WMSingleton @@ -709,6 +713,13 @@ public abstract class WMShellModule { @WMSingleton @Provides + static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler( + Transitions transitions) { + return new ToggleResizeDesktopTaskTransitionHandler(transitions); + } + + @WMSingleton + @Provides static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, Context context 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 efbd52f4824f..2b763ad8f14d 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 @@ -31,6 +31,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.IBinder import android.os.SystemProperties +import android.util.DisplayMetrics.DENSITY_DEFAULT import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE @@ -38,7 +39,6 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo import android.window.TransitionRequestInfo -import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -79,6 +79,8 @@ class DesktopTasksController( private val transitions: Transitions, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, + private val toggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler, private val desktopModeTaskRepository: DesktopModeTaskRepository, private val launchAdjacentController: LaunchAdjacentController, @ShellMainThread private val mainExecutor: ShellExecutor @@ -184,7 +186,7 @@ class DesktopTasksController( ) // Bring other apps to front first bringDesktopAppsToFront(task.displayId, wct) - addMoveToDesktopChanges(wct, task.token) + addMoveToDesktopChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -205,7 +207,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() moveHomeTaskToFront(wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -225,7 +227,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() bringDesktopAppsToFront(taskInfo.displayId, wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -251,7 +253,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { @@ -270,7 +272,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position, mOnAnimationFinishedCallback) @@ -287,7 +289,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( @@ -383,6 +385,49 @@ class DesktopTasksController( } } + /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */ + fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + val destinationBounds = Rect() + if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) { + // The desktop task is currently occupying the whole stable bounds, toggle to the + // default bounds. + getDefaultDesktopTaskBounds( + density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT, + stableBounds = stableBounds, + outBounds = destinationBounds + ) + } else { + // Toggle to the stable bounds. + destinationBounds.set(stableBounds) + } + + val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + taskInfo.taskId, + windowDecor + ) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + + private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) { + val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt() + val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt() + outBounds.set(0, 0, width, height) + // Center the task in stable bounds + outBounds.offset( + stableBounds.centerX() - outBounds.centerX(), + stableBounds.centerY() - outBounds.centerY() + ) + } + /** * Get windowing move for a given `taskId` * @@ -455,36 +500,62 @@ class DesktopTasksController( request ) // Check if we should skip handling this transition + var reason = "" val shouldHandleRequest = when { // Only handle open or to front transitions - request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false + request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { + reason = "transition type not handled (${request.type})" + false + } // Only handle when it is a task transition - request.triggerTask == null -> false + request.triggerTask == null -> { + reason = "triggerTask is null" + false + } // Only handle standard type tasks - request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false + request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> { + reason = "activityType not handled (${request.triggerTask.activityType})" + false + } // Only handle fullscreen or freeform tasks request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && - request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false + request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> { + reason = "windowingMode not handled (${request.triggerTask.windowingMode})" + false + } // Otherwise process it else -> true } if (!shouldHandleRequest) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: skipping handleRequest reason=%s", + reason + ) return null } val task: RunningTaskInfo = request.triggerTask - return when { + val result = 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 + else -> { + null + } } + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest result=%s", + result ?: "null" + ) + return result } /** @@ -507,6 +578,7 @@ class DesktopTasksController( } private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { KtProtoLog.d( @@ -516,13 +588,14 @@ class DesktopTasksController( task.taskId ) return WindowContainerTransaction().also { wct -> - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) } } return null } private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { KtProtoLog.d( @@ -532,7 +605,7 @@ class DesktopTasksController( task.taskId ) return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task.token) + addMoveToDesktopChanges(wct, task) } } return null @@ -546,30 +619,44 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() bringDesktopAppsToFront(task.displayId, wct) - addMoveToDesktopChanges(wct, task.token) + addMoveToDesktopChanges(wct, task) desktopModeTaskRepository.setStashed(task.displayId, false) return wct } private fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM) - wct.reorder(token, true /* onTop */) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) { + // Display windowing is freeform, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FREEFORM + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.reorder(taskInfo.token, true /* onTop */) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getDesktopDensityDpi()) + wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi()) } } private fun addMoveToFullscreenChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN) - wct.setBounds(token, null) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // Display windowing is fullscreen, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FULLSCREEN + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.setBounds(taskInfo.token, null) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getFullscreenDensityDpi()) + wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi()) } } @@ -867,9 +954,17 @@ class DesktopTasksController( companion object { private val DESKTOP_DENSITY_OVERRIDE = - SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0) + SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284) private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000) + // Override default freeform task width when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_WIDTH_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840) + + // Override default freeform task height when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_HEIGHT_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630) + /** * Check if desktop density override is enabled */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt new file mode 100644 index 000000000000..94788e45e2b6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -0,0 +1,154 @@ +/* + * 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 + +import android.animation.Animator +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.graphics.Rect +import android.os.IBinder +import android.util.SparseArray +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import androidx.core.animation.addListener +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import java.util.function.Supplier + +/** Handles the animation of quick resizing of desktop tasks. */ +class ToggleResizeDesktopTaskTransitionHandler( + private val transitions: Transitions, + private val transactionSupplier: Supplier<SurfaceControl.Transaction> +) : Transitions.TransitionHandler { + + private val rectEvaluator = RectEvaluator(Rect()) + private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>() + + private var boundsAnimator: Animator? = null + + constructor( + transitions: Transitions + ) : this(transitions, Supplier { SurfaceControl.Transaction() }) + + /** Starts a quick resize transition. */ + fun startTransition( + wct: WindowContainerTransaction, + taskId: Int, + windowDecoration: DesktopModeWindowDecoration + ) { + // Pause relayout until the transition animation finishes. + windowDecoration.incrementRelayoutBlock() + transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) + taskToDecorationMap.put(taskId, windowDecoration) + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + val change = findRelevantChange(info) + val leash = change.leash + val taskId = change.taskInfo.taskId + val startBounds = change.startAbsBounds + val endBounds = change.endAbsBounds + val windowDecor = + taskToDecorationMap.removeReturnOld(taskId) + ?: throw IllegalStateException("Window decoration not found for task $taskId") + + val tx = transactionSupplier.get() + boundsAnimator?.cancel() + boundsAnimator = + ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds) + .setDuration(RESIZE_DURATION_MS) + .apply { + addListener( + onStart = { + startTransaction + .setPosition( + leash, + startBounds.left.toFloat(), + startBounds.top.toFloat() + ) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash) + windowDecor.showResizeVeil(startTransaction, startBounds) + }, + onEnd = { + finishTransaction + .setPosition( + leash, + endBounds.left.toFloat(), + endBounds.top.toFloat() + ) + .setWindowCrop(leash, endBounds.width(), endBounds.height()) + .show(leash) + windowDecor.hideResizeVeil() + finishCallback.onTransitionFinished(null, null) + boundsAnimator = null + } + ) + addUpdateListener { anim -> + val rect = anim.animatedValue as Rect + tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop(leash, rect.width(), rect.height()) + .show(leash) + windowDecor.updateResizeVeil(tx, rect) + } + start() + } + return true + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + return null + } + + private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { + val matchingChanges = + info.changes.filter { c -> + !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE + } + if (matchingChanges.size != 1) { + throw IllegalStateException( + "Expected 1 relevant change but found: ${matchingChanges.size}" + ) + } + return matchingChanges.first() + } + + private fun isWallpaper(change: TransitionInfo.Change): Boolean { + return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0 + } + + private fun isValidTaskChange(change: TransitionInfo.Change): Boolean { + return change.taskInfo != null && change.taskInfo?.taskId != -1 + } + + companion object { + private const val RESIZE_DURATION_MS = 300L + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 18898f1f2153..4dbb50f34a35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -534,17 +534,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - if (ENABLE_SHELL_TRANSITIONS) { - if (requestEnterSplit && mSplitScreenOptional.isPresent()) { - mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo, - isPipTopLeft() - ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); - mPipTransitionController.startExitTransition( - TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */); - return; - } - } - final Rect displayBounds = mPipBoundsState.getDisplayBounds(); final Rect destinationBounds = new Rect(displayBounds); final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) @@ -553,10 +542,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // For exiting to fullscreen, the windowing mode of task will be changed to fullscreen // until the animation is finished. Otherwise if the activity is resumed and focused at the // begin of aniamtion, the app may do something too early to distub the animation. - final boolean toFullscreen = destinationBounds.equals(displayBounds); - if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS - && !toFullscreen)) { + if (Transitions.SHELL_TRANSITIONS_ROTATION) { // When exit to fullscreen with Shell transition enabled, we update the Task windowing // mode directly so that it can also trigger display rotation and visibility update in // the same transition if there will be any. @@ -588,9 +575,29 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP); if (Transitions.ENABLE_SHELL_TRANSITIONS) { + if (requestEnterSplit && mSplitScreenOptional.isPresent()) { + wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); + mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo, + isPipToTopLeft() + ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); + mPipTransitionController.startExitTransition( + TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds); + return; + } + + if (mSplitScreenOptional.isPresent()) { + // If pip activity will reparent to origin task case and if the origin task still + // under split root, apply exit split transaction to make it expand to fullscreen. + SplitScreenController split = mSplitScreenOptional.get(); + if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) { + split.prepareExitSplitScreen(wct, split.getStageOfTask( + mTaskInfo.lastParentTaskIdBeforePip)); + } + } mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); return; } + if (mSplitScreenOptional.isPresent()) { // If pip activity will reparent to origin task case and if the origin task still under // split root, just exit split screen here to ensure it could expand to fullscreen. @@ -1139,7 +1146,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, public void setPipVisibility(boolean visible) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "setPipVisibility: %s, state=%s visible=%s", - mTaskInfo.topActivity, mPipTransitionState, visible); + (mTaskInfo != null ? mTaskInfo.topActivity : null), mPipTransitionState, visible); if (!isInPip()) { return; } @@ -1666,17 +1673,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } - private boolean isPipTopLeft() { - if (!mSplitScreenOptional.isPresent()) { - return false; - } - final Rect topLeft = new Rect(); - final Rect bottomRight = new Rect(); - mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); - - return topLeft.contains(mPipBoundsState.getBounds()); - } - private boolean isPipToTopLeft() { if (!mSplitScreenOptional.isPresent()) { return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 5086e2c4b05d..86b0f33ad54c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -816,6 +816,12 @@ public class PipTransition extends PipTransitionController { final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); final SurfaceControl leash = pipChange.getLeash(); final int startRotation = pipChange.getStartRotation(); + // Check again in case some callers use startEnterAnimation directly so the flag was not + // set in startAnimation, e.g. from DefaultMixedHandler. + if (!mInFixedRotation) { + mEndFixedRotation = pipChange.getEndFixedRotation(); + mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED; + } final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, @@ -844,7 +850,7 @@ public class PipTransition extends PipTransitionController { && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash, - sourceHintRect, destinationBounds, rotationDelta, taskInfo); + sourceHintRect, destinationBounds, taskInfo); return; } @@ -935,8 +941,15 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, - @NonNull Rect destinationBounds, int rotationDelta, + @NonNull Rect destinationBounds, @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { + if (mInFixedRotation) { + // If rotation changes when returning to home, the transition should contain both the + // entering PiP and the display change (PipController#startSwipePipToHome has updated + // the display layout to new rotation). So it is not expected to see fixed rotation. + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation); + } final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; if (swipePipToHomeOverlay != null) { // Launcher fade in the overlay on top of the fullscreen Task. It is possible we @@ -947,12 +960,7 @@ public class PipTransition extends PipTransitionController { mPipOrganizer.mSwipePipToHomeOverlay = null; } - Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); - if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) { - // PipController#startSwipePipToHome has updated the display layout to new rotation, - // so flip the source bounds to match the same orientation. - sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width()); - } + final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, @@ -981,12 +989,7 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo) { - final int changeSize = info.getChanges().size(); - if (changeSize < 4) { - throw new RuntimeException( - "Got an exit-pip-to-split transition with unexpected change-list"); - } - for (int i = changeSize - 1; i >= 0; i--) { + for (int i = info.getChanges().size() - 1; i >= 0; i--) { final TransitionInfo.Change change = info.getChanges().get(i); final int mode = change.getMode(); @@ -1015,24 +1018,17 @@ public class PipTransition extends PipTransitionController { private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange, @NonNull SurfaceControl.Transaction startTransaction) { final SurfaceControl leash = prevPipTaskChange.getLeash(); - final Rect bounds = prevPipTaskChange.getEndAbsBounds(); - final Point offset = prevPipTaskChange.getEndRelOffset(); - bounds.offset(-offset.x, -offset.y); - - startTransaction.setWindowCrop(leash, null); - startTransaction.setMatrix(leash, 1, 0, 0, 1); - startTransaction.setCornerRadius(leash, 0); - startTransaction.setPosition(leash, bounds.left, bounds.top); - - if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) { - if (mPipAnimationController.getCurrentAnimator() != null) { - mPipAnimationController.getCurrentAnimator().cancel(); - } - startTransaction.setAlpha(leash, 1); - } + startTransaction.remove(leash); + mHasFadeOut = false; mCurrentPipTaskToken = null; - mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo()); + + // clean-up the state in PipTaskOrganizer if the PipTaskOrganizer#onTaskAppeared() hasn't + // been called yet with its leash reference now pointing to a new SurfaceControl not + // matching the leash of the pip we are removing. + if (mPipOrganizer.getSurfaceControl() == leash) { + mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo()); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 2fff0e469f3d..e1bcd70c256b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -38,6 +38,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -223,6 +224,13 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** Whether a particular package is same as current pip package. */ + public boolean isInPipPackage(String packageName) { + final TaskInfo inPipTask = mPipOrganizer.getTaskInfo(); + return packageName != null && inPipTask != null + && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent)); + } + /** Add PiP-related changes to `outWCT` for the given request. */ public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java index fc674a8aa59b..f9332e4bdb2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -63,6 +63,15 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterfac if (pipBoundsState.isImeShowing()) { insets.bottom -= pipBoundsState.getImeHeight(); } + // if PiP is stashed we only adjust the vertical position if it's outside of insets and + // ignore all keep clear areas, since it's already on the side + if (pipBoundsState.isStashed()) { + if (startingBounds.bottom > insets.bottom || startingBounds.top < insets.top) { + // bring PiP back to be aligned by bottom inset + startingBounds.offset(0, insets.bottom - startingBounds.bottom); + } + return startingBounds; + } Rect pipBounds = new Rect(startingBounds); boolean shouldApplyGravity = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index e7a1395f541c..5e1b6becfa45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -31,6 +31,8 @@ import android.os.RemoteException; import android.util.Size; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; import com.android.internal.protolog.common.ProtoLog; @@ -131,6 +133,8 @@ public class PhonePipMenuController implements PipMenuController { private PipMenuView mPipMenuView; + private SurfaceControl mLeash; + private ActionListener mMediaActionListener = new ActionListener() { @Override public void onMediaActionsChanged(List<RemoteAction> mediaActions) { @@ -166,6 +170,7 @@ public class PhonePipMenuController implements PipMenuController { */ @Override public void attach(SurfaceControl leash) { + mLeash = leash; attachPipMenuView(); } @@ -176,6 +181,7 @@ public class PhonePipMenuController implements PipMenuController { public void detach() { hideMenu(); detachPipMenuView(); + mLeash = null; } void attachPipMenuView() { @@ -185,6 +191,36 @@ public class PhonePipMenuController implements PipMenuController { } mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, mSplitScreenController, mPipUiEventLogger); + mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + v.getViewRootImpl().addSurfaceChangedCallback( + new ViewRootImpl.SurfaceChangedCallback() { + @Override + public void surfaceCreated(SurfaceControl.Transaction t) { + final SurfaceControl sc = getSurfaceControl(); + if (sc != null) { + t.reparent(sc, mLeash); + // make menu on top of the surface + t.setLayer(sc, Integer.MAX_VALUE); + } + } + + @Override + public void surfaceReplaced(SurfaceControl.Transaction t) { + } + + @Override + public void surfaceDestroyed() { + } + }); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); @@ -321,30 +357,10 @@ public class PhonePipMenuController implements PipMenuController { return; } - // If there is no pip leash supplied, that means the PiP leash is already finalized - // resizing and the PiP menu is also resized. We then want to do a scale from the current - // new menu bounds. + // TODO(b/286307861) transaction should be applied outside of PiP menu controller if (pipLeash != null && t != null) { - mPipMenuView.getBoundsOnScreen(mTmpSourceBounds); - } else { - mTmpSourceBounds.set(0, 0, destinationBounds.width(), destinationBounds.height()); + t.apply(); } - - mTmpSourceRectF.set(mTmpSourceBounds); - mTmpDestinationRectF.set(destinationBounds); - mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); - final SurfaceControl surfaceControl = getSurfaceControl(); - if (surfaceControl == null) { - return; - } - final SurfaceControl.Transaction menuTx = - mSurfaceControlTransactionFactory.getTransaction(); - menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform); - if (pipLeash != null && t != null) { - // Merge the two transactions, vsyncId has been set on menuTx. - menuTx.merge(t); - } - menuTx.apply(); } /** @@ -362,18 +378,10 @@ public class PhonePipMenuController implements PipMenuController { return; } - final SurfaceControl surfaceControl = getSurfaceControl(); - if (surfaceControl == null) { - return; - } - final SurfaceControl.Transaction menuTx = - mSurfaceControlTransactionFactory.getTransaction(); - menuTx.setCrop(surfaceControl, destinationBounds); + // TODO(b/286307861) transaction should be applied outside of PiP menu controller if (pipLeash != null && t != null) { - // Merge the two transactions, vsyncId has been set on menuTx. - menuTx.merge(t); + t.apply(); } - menuTx.apply(); } private boolean checkPipMenuState() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 9729a4007bac..da455f85d908 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -33,9 +33,10 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import com.android.wm.shell.R; -import com.android.wm.shell.bubbles.DismissView; -import com.android.wm.shell.common.DismissCircleView; +import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissCircleView; +import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.pip.PipUiEventLogger; @@ -106,6 +107,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen } mTargetViewContainer = new DismissView(mContext); + DismissViewUtils.setup(mTargetViewContainer); mTargetView = mTargetViewContainer.getCircle(); mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> { if (!windowInsets.equals(mWindowInsets)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 2a61445b27ba..b0fa9936d879 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -49,7 +49,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SPLIT_SCREEN), WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), - WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 8723f9b0181d..8024a4c22f36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -313,7 +313,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() { int[] taskIds = null; TaskSnapshot[] snapshots = null; - if (mPausingTasks.size() > 0) { + if (mPausingTasks != null && mPausingTasks.size() > 0) { taskIds = new int[mPausingTasks.size()]; snapshots = new TaskSnapshot[mPausingTasks.size()]; try { @@ -595,9 +595,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { cancel(mWillFinishToHome, true /* withScreenshots */, "display change"); return; } - // Don't consider order-only changes as changing apps. - if (!TransitionUtil.isOrderOnly(change)) { + // Don't consider order-only & non-leaf changes as changing apps. + if (!TransitionUtil.isOrderOnly(change) && isLeafTask) { hasChangingApp = true; + } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME + && !mRecentsTask.equals(change.getContainer())) { + // Unless it is a 3p launcher. This means that the 3p launcher was already + // visible (eg. the "pausing" task is translucent over the 3p launcher). + // Treat it as if we are "re-opening" the 3p launcher. + if (openingTasks == null) { + openingTasks = new ArrayList<>(); + openingTaskIsLeafs = new IntArray(); + } + openingTasks.add(change); + openingTaskIsLeafs.add(1); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index e7ef6a0d71d3..e294229038f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -89,6 +89,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -346,6 +347,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; } + /** Get the split stage of task is under it. */ + public @StageType int getStageOfTask(int taskId) { + return mStageCoordinator.getStageOfTask(taskId); + } + /** Check split is foreground and task is under split or not by taskId. */ public boolean isTaskInSplitScreenForeground(int taskId) { return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); @@ -392,17 +398,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */); } - public void enterSplitScreen(int taskId, boolean leftOrTop) { - enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction()); - } - + /** + * Doing necessary window transaction for other transition handler need to enter split in + * transition. + */ public void prepareEnterSplitScreen(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo, int startPosition) { - mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition); + mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition, + false /* resizeAnim */); + } + + /** + * Doing necessary surface transaction for other transition handler need to enter split in + * transition when finished. + */ + public void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { + mStageCoordinator.finishEnterSplitScreen(finishT); } - public void finishEnterSplitScreen(SurfaceControl.Transaction t) { - mStageCoordinator.finishEnterSplitScreen(t); + /** + * Doing necessary window transaction for other transition handler need to exit split in + * transition. + */ + public void prepareExitSplitScreen(WindowContainerTransaction wct, + @StageType int stageToTop) { + mStageCoordinator.prepareExitSplitScreen(stageToTop, wct); + } + + public void enterSplitScreen(int taskId, boolean leftOrTop) { + enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction()); } public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { 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 986309948ada..d21f8a48e62a 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 @@ -199,19 +199,7 @@ class SplitScreenTransitions { boolean isOpening = TransitionUtil.isOpeningType(info.getType()); if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { // fade out - 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 */); - } + 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 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 34e70bb55390..b5d4a9d7e8e1 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 @@ -122,7 +122,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; @@ -173,7 +172,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final SideStage mSideStage; private final StageListenerImpl mSideStageListener = new StageListenerImpl(); - private final DisplayLayout mDisplayLayout; @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -321,7 +319,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, this::onTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); - mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); @@ -359,7 +356,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; mDisplayController.addDisplayWindowListener(this); - mDisplayLayout = new DisplayLayout(); transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); @@ -407,7 +403,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { - prepareEnterSplitScreen(wct, task, stagePosition); + prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */); if (ENABLE_SHELL_TRANSITIONS) { mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, @@ -505,20 +501,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { + mSplitRequest = new SplitRequest(intent.getIntent(), position); if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); - options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); wct.sendPendingIntent(intent, fillInIntent, options); + // If this should be mixed, just send the intent to avoid split handle transition directly. + if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) { + mTaskOrganizer.applyTransaction(wct); + return; + } + // If split screen is not activated, we're expecting to open a pair of apps to split. final int extraTransitType = mMainStage.isActive() ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; - prepareEnterSplitScreen(wct, null /* taskInfo */, position); + prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, extraTransitType, !mIsDropEntering); @@ -575,7 +577,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) { updateWindowBounds(mSplitLayout, wct); } - mSplitRequest = new SplitRequest(intent.getIntent(), position); wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -1463,7 +1464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * an existing WindowContainerTransaction (rather than applying immediately). This is intended * to be used when exiting split might be bundled with other window operations. */ - private void prepareExitSplitScreen(@StageType int stageToTop, + void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { if (!mMainStage.isActive()) return; mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); @@ -1471,7 +1472,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { - prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED); + prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED, + !mIsDropEntering); } /** @@ -1479,17 +1481,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * into side stage. */ void prepareEnterSplitScreen(WindowContainerTransaction wct, - @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) { + @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, + boolean resizeAnim) { onSplitScreenEnter(); if (isSplitActive()) { - prepareBringSplit(wct, taskInfo, startPosition); + prepareBringSplit(wct, taskInfo, startPosition, resizeAnim); } else { - prepareActiveSplit(wct, taskInfo, startPosition); + prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim); } } private void prepareBringSplit(WindowContainerTransaction wct, - @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) { + @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, + boolean resizeAnim) { if (taskInfo != null) { wct.startTask(taskInfo.taskId, resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct)); @@ -1501,33 +1505,38 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // won't guarantee to put the task to the indicated new position. mMainStage.evictAllChildren(wct); mMainStage.reparentTopTask(wct); - prepareSplitLayout(wct); + prepareSplitLayout(wct, resizeAnim); } } private void prepareActiveSplit(WindowContainerTransaction wct, - @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) { + @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, + boolean resizeAnim) { if (!ENABLE_SHELL_TRANSITIONS) { // Legacy transition we need to create divider here, shell transition case we will // create it on #finishEnterSplitScreen mSplitLayout.init(); + } else { + // We handle split visibility itself on shell transition, but sometimes we didn't + // reset it correctly after dismiss by some reason, so just set invisible before active. + setSplitsVisible(false); } if (taskInfo != null) { setSideStagePosition(startPosition, wct); mSideStage.addTask(taskInfo, wct); } mMainStage.activate(wct, true /* includingTopTask */); - prepareSplitLayout(wct); + prepareSplitLayout(wct, resizeAnim); } - private void prepareSplitLayout(WindowContainerTransaction wct) { - if (mIsDropEntering) { - mSplitLayout.resetDividerPosition(); - } else { + private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) { + if (resizeAnim) { mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + } else { + mSplitLayout.resetDividerPosition(); } updateWindowBounds(mSplitLayout, wct); - if (!mIsDropEntering) { + if (resizeAnim) { // Reset its smallest width dp to avoid is change layout before it actually resized to // split bounds. wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, @@ -1537,21 +1546,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setRootForceTranslucent(false, wct); } - void finishEnterSplitScreen(SurfaceControl.Transaction t) { - mSplitLayout.update(t); + void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { + mSplitLayout.update(finishT); mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, getMainStageBounds()); mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash, getSideStageBounds()); - setDividerVisibility(true, t); + setDividerVisibility(true, finishT); // Ensure divider surface are re-parented back into the hierarchy at the end of the // transition. See Transition#buildFinishTransaction for more detail. - t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - t.show(mRootTaskLeash); + updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); + finishT.show(mRootTaskLeash); setSplitsVisible(true); mIsDropEntering = false; + mSplitRequest = null; updateRecentTasksSplitPair(); if (!mLogger.hasStartedSession()) { mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), @@ -1644,7 +1654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); } - if (present && visible) { + if (present) { updateRecentTasksSplitPair(); } @@ -1703,7 +1713,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitLayout == null) { mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mRootTaskInfo.configuration, this, mParentContainerCallbacks, - mDisplayImeController, mTaskOrganizer, + mDisplayController, mDisplayImeController, mTaskOrganizer, PARALLAX_ALIGN_CENTER /* parallaxType */); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } @@ -2165,8 +2175,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (displayId != DEFAULT_DISPLAY) { return; } - mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId)); - if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi) && mMainStage.isActive() && mSplitLayout.updateConfiguration(newConfig) @@ -2183,10 +2191,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onDisplayChange(int displayId, int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { - if (!mMainStage.isActive()) return; + if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return; - mDisplayLayout.rotateTo(mContext.getResources(), toRotation); - mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets()); + mSplitLayout.rotateTo(toRotation); if (newDisplayAreaInfo != null) { mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration); } @@ -2329,7 +2336,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (!isSplitScreenVisible() && isOpening) { // If split is running in the background and the trigger task is appearing into // split, prepare to enter split screen. - setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out); prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); @@ -2355,7 +2361,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); - setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out); prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); @@ -2512,6 +2517,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, startTransaction, finishTransaction, finishCallback)) { mSplitLayout.update(startTransaction); + startTransaction.apply(); return true; } } @@ -2650,11 +2656,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitTransitions.mPendingEnter.mExtraTransitType == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { + // Open to side should only be used when split already active and foregorund. if (mainChild == null && sideChild == null) { Log.w(TAG, "Launched a task in split, but didn't receive any task in transition."); - mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT) - -> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct)); - mSplitUnsupportedToast.show(); + // This should happen when the target app is already on front, so just cancel. + mSplitTransitions.mPendingEnter.cancel(null); return true; } } else { @@ -2896,6 +2902,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Call this when the recents animation canceled during split-screen. */ public void onRecentsInSplitAnimationCanceled() { mPausingTasks.clear(); + setSplitsVisible(false); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + true /* reparentLeafTaskIfRelaunch */); + mTaskOrganizer.applyTransaction(wct); } /** Call this when the recents animation during split-screen finishes. */ @@ -2922,7 +2934,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSplitsVisible(false); finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, true /* reparentLeafTaskIfRelaunch */); - logExit(EXIT_REASON_UNKNOWN); } /** Call this when the recents animation finishes by doing pair-to-pair switch. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 478083607aac..3ef4f024a8ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -132,7 +132,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { * Returns the top visible child task's id. */ int getTopVisibleChildTaskId() { - final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible); + final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible + && t.isVisibleRequested); return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID; } @@ -188,12 +189,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { final int taskId = taskInfo.taskId; mChildrenLeashes.put(taskId, leash); mChildrenTaskInfo.put(taskId, taskInfo); - updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); - mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible); + mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, + taskInfo.isVisible && taskInfo.isVisibleRequested); if (ENABLE_SHELL_TRANSITIONS) { // Status is managed/synchronized by the transition lifecycle. return; } + updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); mCallbacks.onChildTaskAppeared(taskId); sendStatusChanged(); } else { @@ -229,7 +231,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, - taskInfo.isVisible); + taskInfo.isVisible && taskInfo.isVisibleRequested); if (!ENABLE_SHELL_TRANSITIONS) { updateChildTaskSurface( taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); @@ -423,7 +425,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } t.setCrop(leash, null); t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); - if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) { + if (firstAppeared) { t.setAlpha(leash, 1f); t.setMatrix(leash, 1, 0, 0, 1); t.show(leash); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index ae722208782e..4cfbbd971fe3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -370,8 +371,11 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { mStartingWindowRecordManager.addRecord(taskId, tView); } - private void removeWindowInner(View decorView, boolean hideView) { + private void removeWindowInner(@NonNull View decorView, boolean hideView) { requestTopUi(false); + if (!decorView.isAttachedToWindow()) { + return; + } if (hideView) { decorView.setVisibility(View.GONE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 1bbd3679948b..163cf501734c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -61,6 +61,16 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { private TaskViewBase mTaskViewBase; private final Context mContext; + /** + * There could be a situation where we have task info and receive + * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the + * activity might fail to open, and in this case we need to clean up the task view / notify + * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared + * in this situation to allow us to notify listeners correctly if the task failed to open. + */ + private ActivityManager.RunningTaskInfo mPendingInfo; + /* Indicates that the task we attempted to launch in the task view failed to launch. */ + private boolean mTaskNotFound; protected ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; private SurfaceControl mTaskLeash; @@ -236,6 +246,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mTaskInfo = null; mTaskToken = null; mTaskLeash = null; + mPendingInfo = null; + mTaskNotFound = false; } private void updateTaskVisibility() { @@ -257,6 +269,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { if (isUsingShellTransitions()) { + mPendingInfo = taskInfo; + if (mTaskNotFound) { + // If we were already notified by shell transit that we don't have the + // the task, clean it up now. + cleanUpPendingTask(); + } // Everything else handled by enter transition. return; } @@ -455,6 +473,42 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { return mTaskInfo; } + /** + * Indicates that the task was not found in the start animation for the transition. + * In this case we should clean up the task if we have the pending info. If we don't + * have the pending info, we'll do it when we receive it in + * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}. + */ + void setTaskNotFound() { + mTaskNotFound = true; + if (mPendingInfo != null) { + cleanUpPendingTask(); + } + } + + /** + * Called when a task failed to open and we need to clean up task view / + * notify users of task view. + */ + void cleanUpPendingTask() { + if (mPendingInfo != null) { + if (mListener != null) { + final int taskId = mPendingInfo.taskId; + mListenerExecutor.execute(() -> { + mListener.onTaskRemovalStarted(taskId); + }); + } + mTaskViewBase.onTaskVanished(mPendingInfo); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false); + + // Make sure the task is removed + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(mPendingInfo.token); + mTaskViewTransitions.closeTaskView(wct, this); + } + resetTaskInfo(); + } + void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) { if (mTaskToken == null) { // Nothing to update, task is not yet available @@ -492,6 +546,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { @NonNull SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct) { + mPendingInfo = null; mTaskInfo = taskInfo; mTaskToken = mTaskInfo.token; mTaskLeash = leash; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 2e7fca3f2b46..5baf2e320227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -139,7 +139,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { * `taskView`. * @param taskView the pending transition should be for this. */ - private PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) { + @VisibleForTesting + PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; if (TransitionUtil.isOpeningType(mPending.get(i).mType)) { @@ -398,10 +399,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } } if (stillNeedsMatchingLaunch) { - throw new IllegalStateException("Expected a TaskView launch in this transition but" - + " didn't get one."); - } - if (wct == null && pending == null && changesHandled != info.getChanges().size()) { + Slog.w(TAG, "Expected a TaskView launch in this transition but didn't get one, " + + "cleaning up the task view"); + // Didn't find a task so the task must have never launched + pending.mTaskView.setTaskNotFound(); + } else if (wct == null && pending == null && changesHandled != info.getChanges().size()) { // Just some house-keeping, let another handler animate. return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index f58f24be4984..6013754b97d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -24,14 +24,15 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.PendingIntent; import android.os.IBinder; -import android.util.Log; import android.util.Pair; import android.view.SurfaceControl; import android.view.WindowManager; @@ -44,6 +45,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.phone.PipTouchHandler; @@ -52,6 +54,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -71,6 +74,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private final KeyguardTransitionHandler mKeyguardHandler; private DesktopModeController mDesktopModeController; private DesktopTasksController mDesktopTasksController; + private UnfoldTransitionHandler mUnfoldHandler; private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; @@ -90,6 +94,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Recents Transition while in desktop mode. */ static final int TYPE_RECENTS_DURING_DESKTOP = 6; + /** Fuld/Unfold transition. */ + static final int TYPE_UNFOLD = 7; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -143,7 +150,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopModeController> desktopModeControllerOptional, - Optional<DesktopTasksController> desktopTasksControllerOptional) { + Optional<DesktopTasksController> desktopTasksControllerOptional, + Optional<UnfoldTransitionHandler> unfoldHandler) { mPlayer = player; mKeyguardHandler = keyguardHandler; if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent() @@ -162,6 +170,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } mDesktopModeController = desktopModeControllerOptional.orElse(null); mDesktopTasksController = desktopTasksControllerOptional.orElse(null); + mUnfoldHandler = unfoldHandler.orElse(null); }, this); } } @@ -170,7 +179,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) { + if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive() + && request.getTriggerTask() != null && mSplitHandler.getSplitItemPosition( + request.getTriggerTask().token) != SPLIT_POSITION_UNDEFINED) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while " + "Split-Screen is active, so treat it as Mixed."); if (request.getRemoteTransition() != null) { @@ -225,6 +236,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; + } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { + final WindowContainerTransaction wct = + mUnfoldHandler.handleRequest(transition, request); + if (wct != null) { + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_UNFOLD, transition); + mixed.mLeftoversHandler = mUnfoldHandler; + mActiveTransitions.add(mixed); + } + return wct; } return null; } @@ -330,6 +351,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { + return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback); } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " @@ -637,6 +660,44 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return false; } + private boolean animateUnfold(@NonNull final MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + mixed.mInFlightSubAnimations--; + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + finishCallback.onTransitionFinished(wct, wctCB); + }; + mixed.mInFlightSubAnimations = 1; + if (!mUnfoldHandler.startAnimation( + mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) { + return false; + } + // Sync pip state. + if (mPipHandler != null) { + // We don't know when to apply `startTransaction` so use a separate transaction here. + // This should be fine because these surface properties are independent. + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + mPipHandler.syncPipSurfaceState(info, t, finishTransaction); + t.apply(); + } + return true; + } + + /** Use to when split use intent to enter, check if this enter transition should be mixed or + * not.*/ + public boolean shouldSplitEnterMixed(PendingIntent intent) { + // Check if this intent package is same as pip one or not, if true we want let the pip + // task enter split. + if (mPipHandler != null) { + return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent())); + } + return false; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -683,6 +744,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { + mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); @@ -710,6 +773,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { + mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 3bf278cc46cf..e52fd00e7df7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -260,6 +260,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // This is the only way to get display-id currently, so check display capabilities here. final DisplayLayout displayLayout = displayController.getDisplayLayout( topTaskInfo.displayId); + // This condition should be true when using gesture navigation or the screen size is large + // (>600dp) because the bar is small relative to screen. + if (displayLayout.allowSeamlessRotationDespiteNavBarMoving()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " nav bar allows seamless."); + return ROTATION_ANIMATION_SEAMLESS; + } // For the upside down rotation we don't rotate seamlessly as the navigation bar moves // position. Note most apps (using orientation:sensor or user as opposed to fullSensor) // will not enter the reverse portrait orientation, so actually the orientation won't @@ -272,13 +278,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return animationHint; } - // If the navigation bar can't change sides, then it will jump when we change orientations - // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation - // where the navbar is low-profile enough that this isn't very noticeable. - if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving() - && (!(displayLayout.navigationBarCanMove() - && (displayChange.getStartAbsBounds().width() - != displayChange.getStartAbsBounds().height())))) { + // If the navigation bar cannot change sides, then it will jump when changing orientation + // so do not use seamless rotation. + if (!displayLayout.navigationBarCanMove()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " nav bar changes sides, so not seamless."); return animationHint; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index cdc82eadcd90..d8a88770072d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -161,6 +161,10 @@ public class Transitions implements RemoteCallable<Transitions>, public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 13; + /** Transition type to animate the toggle resize between the max and default desktop sizes. */ + public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE = + WindowManager.TRANSIT_FIRST_CUSTOM + 14; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; @@ -809,6 +813,9 @@ public class Transitions implements RemoteCallable<Transitions>, track.mReadyTransitions.remove(0); track.mActiveTransition = ready; if (ready.mAborted) { + if (ready.mStartT != null) { + ready.mStartT.apply(); + } // finish now since there's nothing to animate. Calls back into processReadyQueue onFinish(ready, null, null); return; @@ -936,10 +943,6 @@ public class Transitions implements RemoteCallable<Transitions>, /** Aborts a transition. This will still queue it up to maintain order. */ private void onAbort(ActiveTransition transition) { final Track track = mTracks.get(transition.getTrack()); - // apply immediately since they may be "parallel" operations: We currently we use abort for - // thing which are independent to other transitions (like starting-window transfer). - transition.mStartT.apply(); - transition.mFinishT.apply(); transition.mAborted = true; mTracer.logAborted(transition.mInfo.getDebugId()); @@ -1086,9 +1089,8 @@ public class Transitions implements RemoteCallable<Transitions>, if (wct == null) { wct = new WindowContainerTransaction(); } - mDisplayController.getChangeController().dispatchOnDisplayChange(wct, - change.getDisplayId(), change.getStartRotation(), - change.getEndRotation(), null /* newDisplayAreaInfo */); + mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(), + change.getStartRotation(), change.getEndRotation()); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index c504f57216f3..f148412205bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; +import android.app.ActivityManager; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -178,18 +179,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { if (info.getType() == TRANSIT_CHANGE) { + // TODO (b/286928742) unfold transition handler should be part of mixed handler to + // handle merges better. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { + // Tasks that are always on top (e.g. bubbles), will handle their own transition + // as they are on top of everything else. So skip merging transitions here. + return; + } + } // Apply changes happening during the unfold animation immediately t.apply(); finishCallback.onTransitionFinished(null, null); } } + /** Whether `request` contains an unfold action. */ + public boolean hasUnfold(@NonNull TransitionRequestInfo request) { + return (request.getType() == TRANSIT_CHANGE + && request.getDisplayChange() != null + && request.getDisplayChange().isPhysicalDisplayChanged()); + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null - && request.getDisplayChange().isPhysicalDisplayChanged()) { + if (hasUnfold(request)) { mTransition = transition; return new WindowContainerTransaction(); } 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 c33a633f2068..936faa3ee6bf 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 @@ -170,6 +170,9 @@ public class TransitionUtil { if (isOpeningType(mode)) { t.setAlpha(leash, 0.f); } + // Set the transition leash position to 0 in case the divider leash position being + // taking down. + t.setPosition(leash, 0, 0); t.setLayer(leash, Integer.MAX_VALUE); return; } @@ -228,7 +231,11 @@ public class TransitionUtil { t.reparent(change.getLeash(), leashSurface); t.setAlpha(change.getLeash(), 1.0f); t.show(change.getLeash()); - t.setPosition(change.getLeash(), 0, 0); + if (!isDividerBar(change)) { + // For divider, don't modify its inner leash position when creating the outer leash + // for the transition. In case the position being wrong after the transition finished. + t.setPosition(change.getLeash(), 0, 0); + } t.setLayer(change.getLeash(), 0); return leashSurface; } 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 b4a1274f3e82..92c2a7c03ee4 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 @@ -33,6 +33,8 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -258,7 +260,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if a drag is happening; or {@code false} if it is not */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index cea0fcb2a9c3..b217bd39a446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -143,6 +143,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mHandler, mChoreographer, mDisplay.getDisplayId(), + 0 /* taskCornerRadius */, mDecorationContainerSurface, mDragPositioningCallback); } 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 4e61aafa59a5..c93a11d85f7e 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 @@ -43,6 +43,7 @@ import android.os.IBinder; import android.os.Looper; import android.util.SparseArray; import android.view.Choreographer; +import android.view.GestureDetector; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; @@ -198,7 +199,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (change.getMode() == WindowManager.TRANSIT_CHANGE && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE)) { + || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); } @@ -279,15 +281,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private class DesktopModeTouchEventListener implements - View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener + implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { private final int mTaskId; private final WindowContainerToken mTaskToken; private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; + private final GestureDetector mGestureDetector; private boolean mIsDragging; + private boolean mShouldClick; private int mDragPointerId = -1; private DesktopModeTouchEventListener( @@ -297,6 +301,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); + mGestureDetector = new GestureDetector(mContext, this); } @Override @@ -357,7 +362,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - return mDragDetector.onMotionEvent(e); + return mDragDetector.onMotionEvent(v, e); } private void moveTaskToFront(RunningTaskInfo taskInfo) { @@ -372,7 +377,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if the motion event is handled. */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (DesktopModeStatus.isProto2Enabled() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { @@ -383,6 +388,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { == WINDOWING_MODE_FULLSCREEN) { return false; } + if (mGestureDetector.onTouchEvent(e)) { + return true; + } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); @@ -390,7 +398,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); mIsDragging = false; - return false; + mShouldClick = true; + return true; } case MotionEvent.ACTION_MOVE: { final DesktopModeWindowDecoration decoration = @@ -404,10 +413,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; + mShouldClick = false; return true; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + final boolean wasDragging = mIsDragging; + if (!wasDragging) { + if (mShouldClick && v != null) { + v.performClick(); + mShouldClick = false; + return true; + } + return false; + } if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); } @@ -422,13 +441,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId))); - final boolean wasDragging = mIsDragging; mIsDragging = false; - return wasDragging; + return true; } } return true; } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + mDesktopTasksController.ifPresent(c -> { + c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)); + }); + return true; + } } // InputEventReceiver to listen for touch input outside of caption bounds @@ -813,6 +840,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMainChoreographer, mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( windowDecoration, taskInfo); @@ -843,7 +871,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDisplayController, disallowedAreaForEndBounds, mDragStartListener, mTransactionFactory); } else { - windowDecoration.createResizeVeil(); return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController, disallowedAreaForEndBounds, mDragStartListener, mTransitions); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 5cb7d4836a57..bc89385a0d13 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -39,6 +38,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.window.WindowContainerTransaction; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -182,11 +182,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - final TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.dialogCornerRadius}); - mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0); - ta.recycle(); - + mRelayoutParams.mCornerRadius = + (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -235,6 +232,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandler, mChoreographer, mDisplay.getDisplayId(), + mRelayoutParams.mCornerRadius, mDecorationContainerSurface, mDragPositioningCallback); } @@ -294,23 +292,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Fade in the resize veil + * Show the resize veil. */ - void showResizeVeil(Rect taskBounds) { + public void showResizeVeil(Rect taskBounds) { mResizeVeil.showVeil(mTaskSurface, taskBounds); } /** + * Show the resize veil. + */ + public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) { + mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */); + } + + /** * Set new bounds for the resize veil */ - void updateResizeVeil(Rect newBounds) { + public void updateResizeVeil(Rect newBounds) { mResizeVeil.updateResizeVeil(newBounds); } /** + * Set new bounds for the resize veil + */ + public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) { + mResizeVeil.updateResizeVeil(tx, newBounds); + } + + /** * Fade the resize veil out. */ - void hideResizeVeil() { + public void hideResizeVeil() { mResizeVeil.hideVeil(); } 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 58644b23ce12..da268988bac7 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 @@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; /** * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow @@ -54,14 +57,24 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(MotionEvent ev) { + return onMotionEvent(null /* view */, ev); + } + + /** + * The receiver of the {@link MotionEvent} flow. + * + * @return the result returned by {@link #mEventHandler}, or the result when + * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed + */ + boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; if (!isTouchScreen) { // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered // to take the slop threshold into consideration. - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } switch (ev.getActionMasked()) { case ACTION_DOWN: { @@ -69,7 +82,7 @@ class DragDetector { float rawX = ev.getRawX(0); float rawY = ev.getRawY(0); mInputDownPoint.set(rawX, rawY); - mResultOfDownAction = mEventHandler.handleMotionEvent(ev); + mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev); return mResultOfDownAction; } case ACTION_MOVE: { @@ -87,7 +100,7 @@ class DragDetector { // The event handler should only be notified about 'move' events if a drag has been // detected. if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } else { return mResultOfDownAction; } @@ -95,10 +108,10 @@ class DragDetector { case ACTION_UP: case ACTION_CANCEL: { resetState(); - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } default: - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } } @@ -114,6 +127,6 @@ class DragDetector { } interface MotionEventHandler { - boolean handleMotionEvent(MotionEvent ev); + boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 287d86187288..68b63e62f09b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -42,6 +42,7 @@ import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.SurfaceControl; +import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; @@ -55,7 +56,7 @@ import com.android.internal.view.BaseIWindow; */ class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; - + private static final float TOP_CORNER_PADDING = 1.5f; private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); private final Handler mHandler; private final Choreographer mChoreographer; @@ -73,6 +74,7 @@ class DragResizeInputListener implements AutoCloseable { private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; + private int mTaskCornerRadius; private Rect mLeftTopCornerBounds; private Rect mRightTopCornerBounds; @@ -87,12 +89,14 @@ class DragResizeInputListener implements AutoCloseable { Handler handler, Choreographer choreographer, int displayId, + int taskCornerRadius, SurfaceControl decorationSurface, DragPositioningCallback callback) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; mDisplayId = displayId; + mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; // Use a fake window as the backing surface is a container layer and we don't want to create // a buffer layer for it so we can't use ViewRootImpl. @@ -303,7 +307,7 @@ class DragResizeInputListener implements AutoCloseable { } @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(View v, MotionEvent e) { boolean result = false; // Check if this is a touch event vs mouse event. // Touch events are tracked in four corners. Other events are tracked in resize edges. @@ -383,19 +387,67 @@ class DragResizeInputListener implements AutoCloseable { @DragPositioningCallback.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < 0) { + // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with + // sides will use the bounds specified in setGeometry and not go into task bounds. + if (x < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_LEFT; } - if (x > mTaskWidth) { + if (x > mTaskWidth - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_RIGHT; } - if (y < 0) { + if (y < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_TOP; } - if (y > mTaskHeight) { + if (y > mTaskHeight - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - return ctrlType; + return checkDistanceFromCenter(ctrlType, x, y); + } + + // If corner input is not within appropriate distance of corner radius, do not use it. + // If input is not on a corner or is within valid distance, return ctrlType. + @DragPositioningCallback.CtrlType + private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, + float x, float y) { + int centerX; + int centerY; + + // Determine center of rounded corner circle; this is simply the corner if radius is 0. + switch (ctrlType) { + case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: { + centerX = mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: { + centerX = mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + default: { + return ctrlType; + } + } + double distanceFromCenter = Math.hypot(x - centerX, y - centerY); + + // TODO(b/286461778): Remove this when input in top corner gap no longer goes to header + float cornerPadding = (ctrlType & CTRL_TYPE_TOP) != 0 ? TOP_CORNER_PADDING : 1; + + if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness * cornerPadding + && distanceFromCenter >= mTaskCornerRadius) { + return ctrlType; + } + return 0; } @DragPositioningCallback.CtrlType diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 82771095cd82..bfce72bcadf0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -102,11 +102,17 @@ public class ResizeVeil { } /** - * Animate veil's alpha to 1, fading it in. + * Shows the veil surface/view. + * + * @param t the transaction to apply in sync with the veil draw + * @param parentSurface the surface that the veil should be a child of + * @param taskBounds the bounds of the task that owns the veil + * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown + * immediately */ - public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, + Rect taskBounds, boolean fadeIn) { // Parent surface can change, ensure it is up to date. - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); if (!parentSurface.equals(mParentSurface)) { t.reparent(mVeilSurface, parentSurface); mParentSurface = parentSurface; @@ -115,22 +121,36 @@ public class ResizeVeil { int backgroundColorId = getBackgroundColorId(); mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId)); - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(0f, 1f); - animator.setDuration(RESIZE_ALPHA_DURATION); - animator.addUpdateListener(animation -> { - t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); - t.apply(); - }); - relayout(taskBounds, t); - t.show(mVeilSurface) - .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start()) - .setAlpha(mVeilSurface, 0); + if (fadeIn) { + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(0f, 1f); + animator.setDuration(RESIZE_ALPHA_DURATION); + animator.addUpdateListener(animation -> { + t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); + t.apply(); + }); + + t.show(mVeilSurface) + .addTransactionCommittedListener( + mContext.getMainExecutor(), () -> animator.start()) + .setAlpha(mVeilSurface, 0); + } else { + // Show the veil immediately at full opacity. + t.show(mVeilSurface).setAlpha(mVeilSurface, 1); + } mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } /** + * Animate veil's alpha to 1, fading it in. + */ + public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + showVeil(t, parentSurface, taskBounds, true /* fadeIn */); + } + + /** * Update veil bounds to match bounds changes. * @param newBounds bounds to update veil to. */ @@ -147,6 +167,16 @@ public class ResizeVeil { */ public void updateResizeVeil(Rect newBounds) { SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + updateResizeVeil(t, newBounds); + } + + /** + * Calls relayout to update task and veil bounds. + * + * @param t a transaction to be applied in sync with the veil draw. + * @param newBounds bounds to update veil to. + */ + public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index ee407c07b47c..4407f2ec3167 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -267,6 +267,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) + .setShadowRadius(mTaskSurface, shadowRadius) .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { startT.setCornerRadius(mTaskSurface, params.mCornerRadius); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java index 9da5ab6f71dd..145c8f0ab8af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; @@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations; public class DividerViewTest extends ShellTestCase { private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; + private @Mock DisplayController mDisplayController; private @Mock DisplayImeController mDisplayImeController; private @Mock ShellTaskOrganizer mTaskOrganizer; private SplitLayout mSplitLayout; @@ -62,8 +64,8 @@ public class DividerViewTest extends ShellTestCase { MockitoAnnotations.initMocks(this); Configuration configuration = getConfiguration(); mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, - mSplitLayoutHandler, mCallbacks, mDisplayImeController, mTaskOrganizer, - SplitLayout.PARALLAX_NONE); + mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, + mTaskOrganizer, SplitLayout.PARALLAX_NONE); SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", mContext, configuration, mCallbacks); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 3d779481d361..443cea245a4f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityManager; @@ -41,6 +42,7 @@ import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; @@ -57,6 +59,7 @@ import org.mockito.MockitoAnnotations; public class SplitLayoutTests extends ShellTestCase { @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; + @Mock DisplayController mDisplayController; @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock WindowContainerTransaction mWct; @@ -72,6 +75,7 @@ public class SplitLayoutTests extends ShellTestCase { getConfiguration(), mSplitLayoutHandler, mCallbacks, + mDisplayController, mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_NONE)); @@ -100,6 +104,10 @@ public class SplitLayoutTests extends ShellTestCase { // Verify updateConfiguration returns true if the density changed. config.densityDpi = 123; assertThat(mSplitLayout.updateConfiguration(config)).isTrue(); + + // Verify updateConfiguration checks the current DisplayLayout + verify(mDisplayController, times(5)) // init * 1 + updateConfiguration * 4 + .getDisplayLayout(anyInt()); } @Test @@ -168,6 +176,14 @@ public class SplitLayoutTests extends ShellTestCase { verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt()); } + @Test + public void testRoateTo_checksDisplayLayout() { + mSplitLayout.rotateTo(90); + + verify(mDisplayController, times(2)) // init * 1 + rotateTo * 1 + .getDisplayLayout(anyInt()); + } + private void waitDividerFlingFinished() { verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(), mRunnableCaptor.capture()); 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 dba21b8dd3f3..1477cf7415cf 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 @@ -89,6 +89,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var transitions: Transitions @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler + @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController private lateinit var mockitoSession: StaticMockitoSession @@ -129,6 +131,7 @@ class DesktopTasksControllerTest : ShellTestCase() { transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + mToggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, launchAdjacentController, shellExecutor @@ -270,8 +273,9 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop() { + fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToDesktop(task) val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -279,6 +283,16 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { + val task = setUpFullscreenTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToDesktop(task) + val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test fun moveToDesktop_nonExistentTask_doesNothing() { controller.moveToDesktop(999) verifyWCTNotExecuted() @@ -325,12 +339,23 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen() { + fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task) val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() { + val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToFullscreen(task) + val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java index 4d7e9e450ceb..cc9e26b2c4f1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java @@ -18,6 +18,9 @@ package com.android.wm.shell.pip.phone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -26,10 +29,13 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipBoundsState; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import java.util.Set; @@ -42,6 +48,10 @@ import java.util.Set; public class PhonePipKeepClearAlgorithmTest extends ShellTestCase { private PhonePipKeepClearAlgorithm mPipKeepClearAlgorithm; + + @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private PipBoundsState mMockPipBoundsState; + private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000); @Before @@ -73,7 +83,6 @@ public class PhonePipKeepClearAlgorithmTest extends ShellTestCase { @Test public void findUnoccludedPosition_withCollidingUnrestrictedKeepClearArea_moveBounds() { - // TODO(b/183746978): update this test to accommodate for the updated algorithm final Rect inBounds = new Rect(0, 0, 100, 100); final Rect keepClearRect = new Rect(50, 50, 150, 150); @@ -93,4 +102,202 @@ public class PhonePipKeepClearAlgorithmTest extends ShellTestCase { assertEquals(inBounds, outBounds); } + + @Test + public void adjust_withCollidingRestrictedKeepClearArea_moveBounds() { + final Rect pipBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(50, 50, 150, 150); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.getRestrictedKeepClearAreas()).thenReturn(Set.of(keepClearRect)); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertFalse(outBounds.contains(keepClearRect)); + } + + @Test + public void adjust_withNonCollidingRestrictedKeepClearArea_boundsUnchanged() { + final Rect pipBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(100, 100, 150, 150); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.getRestrictedKeepClearAreas()).thenReturn(Set.of(keepClearRect)); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertFalse(outBounds.contains(keepClearRect)); + } + + @Test + public void adjust_withCollidingRestrictedKeepClearArea_whileStashed_boundsUnchanged() { + final Rect pipBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(50, 50, 150, 150); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.isStashed()).thenReturn(true); + when(mMockPipBoundsState.getRestrictedKeepClearAreas()).thenReturn(Set.of(keepClearRect)); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(pipBounds, outBounds); + } + + @Test + public void adjust_withNonCollidingRestrictedKeepClearArea_whileStashed_boundsUnchanged() { + final Rect pipBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(100, 100, 150, 150); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.isStashed()).thenReturn(true); + when(mMockPipBoundsState.getRestrictedKeepClearAreas()).thenReturn(Set.of(keepClearRect)); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(pipBounds, outBounds); + } + + @Test + public void adjust_aboveDisplayBounds_onLeftEdge_appliesBottomLeftGravity() { + final Rect pipBounds = new Rect( + 0, DISPLAY_BOUNDS.top - 50, 100, DISPLAY_BOUNDS.top + 50); + final Rect expected = new Rect( + 0, DISPLAY_BOUNDS.bottom - 100, 100, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + when(mMockPipBoundsAlgorithm.getSnapFraction(any(Rect.class))).thenReturn(0f); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } + + @Test + public void adjust_belowDisplayBounds_onLeftEdge_appliesBottomLeftGravity() { + final Rect pipBounds = new Rect( + 0, DISPLAY_BOUNDS.bottom - 50, 100, DISPLAY_BOUNDS.bottom + 50); + final Rect expected = new Rect( + 0, DISPLAY_BOUNDS.bottom - 100, 100, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + when(mMockPipBoundsAlgorithm.getSnapFraction(any(Rect.class))).thenReturn(3f); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } + + @Test + public void adjust_aboveDisplayBounds_onRightEdge_appliesBottomRightGravity() { + final Rect pipBounds = new Rect( + DISPLAY_BOUNDS.right - 100, DISPLAY_BOUNDS.top - 50, + DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.top + 50); + final Rect expected = new Rect( + DISPLAY_BOUNDS.right - 100, DISPLAY_BOUNDS.bottom - 100, + DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + when(mMockPipBoundsAlgorithm.getSnapFraction(any(Rect.class))).thenReturn(1f); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } + + @Test + public void adjust_belowDisplayBounds_onRightEdge_appliesBottomRightGravity() { + final Rect pipBounds = new Rect( + DISPLAY_BOUNDS.right - 100, DISPLAY_BOUNDS.bottom - 50, + DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom + 50); + final Rect expected = new Rect( + DISPLAY_BOUNDS.right - 100, DISPLAY_BOUNDS.bottom - 100, + DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + when(mMockPipBoundsAlgorithm.getSnapFraction(any(Rect.class))).thenReturn(2f); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } + + @Test + public void adjust_whileStashed_aboveDisplayBounds_alignsToBottomInset() { + final Rect pipBounds = new Rect( + 0, DISPLAY_BOUNDS.top - 50, 100, DISPLAY_BOUNDS.top + 50); + final Rect expected = new Rect( + 0, DISPLAY_BOUNDS.bottom - 100, 100, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.isStashed()).thenReturn(true); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } + + @Test + public void adjust_whileStashed_belowDisplayBounds_alignsToBottomInset() { + final Rect pipBounds = new Rect( + 0, DISPLAY_BOUNDS.bottom - 50, 100, DISPLAY_BOUNDS.bottom + 50); + final Rect expected = new Rect( + 0, DISPLAY_BOUNDS.bottom - 100, 100, DISPLAY_BOUNDS.bottom); + when(mMockPipBoundsState.getBounds()).thenReturn(pipBounds); + when(mMockPipBoundsState.isStashed()).thenReturn(true); + doAnswer(invocation -> { + Rect arg0 = invocation.getArgument(0); + arg0.set(DISPLAY_BOUNDS); + return null; + }).when(mMockPipBoundsAlgorithm).getInsetBounds(any(Rect.class)); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust( + mMockPipBoundsState, mMockPipBoundsAlgorithm); + + assertEquals(expected, outBounds); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index f1c0c145b31e..e59d09cd1ee1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -162,7 +162,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); verify(mMainStage).reparentTopTask(eq(wct)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); @@ -180,7 +180,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); } @@ -192,7 +192,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index 81fc8438eec0..1b389565c066 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -128,8 +128,8 @@ public class TaskViewTest extends ShellTestCase { doReturn(true).when(mTransitions).isRegistered(); } mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions)); - mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer, - mTaskViewTransitions, mSyncQueue); + mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer, + mTaskViewTransitions, mSyncQueue)); mTaskView = new TaskView(mContext, mTaskViewTaskController); mTaskView.setListener(mExecutor, mViewListener); } @@ -544,4 +544,23 @@ public class TaskViewTest extends ShellTestCase { mTaskView.removeTask(); verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController)); } + + @Test + public void testOnTaskAppearedWithTaskNotFound() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + mTaskViewTaskController.setTaskNotFound(); + mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash); + + verify(mTaskViewTaskController).cleanUpPendingTask(); + verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController)); + } + + @Test + public void testOnTaskAppeared_withoutTaskNotFound() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash); + verify(mTaskViewTaskController, never()).cleanUpPendingTask(); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java index 71ad0d79eaca..03ed18c86608 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.taskview; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.google.common.truth.Truth.assertThat; @@ -25,16 +26,19 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.graphics.Rect; +import android.os.IBinder; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.transition.Transitions; @@ -45,6 +49,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; @SmallTest @@ -295,4 +300,34 @@ public class TaskViewTransitionsTest extends ShellTestCase { mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, new Rect(0, 0, 100, 100)); } + + @Test + public void test_startAnimation_setsTaskNotFound() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + when(change.getTaskInfo()).thenReturn(mTaskInfo); + when(change.getMode()).thenReturn(TRANSIT_OPEN); + + List<TransitionInfo.Change> changes = new ArrayList<>(); + changes.add(change); + + TransitionInfo info = mock(TransitionInfo.class); + when(info.getChanges()).thenReturn(changes); + + mTaskViewTransitions.startTaskView(new WindowContainerTransaction(), + mTaskViewTaskController, + mock(IBinder.class)); + + TaskViewTransitions.PendingTransition pending = + mTaskViewTransitions.findPendingOpeningTransition(mTaskViewTaskController); + + mTaskViewTransitions.startAnimation(pending.mClaimed, + info, + new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), + mock(Transitions.TransitionFinishCallback.class)); + + verify(mTaskViewTaskController).setTaskNotFound(); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 963632b1f8f6..ff380e92322d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -51,7 +51,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.after; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; @@ -93,7 +92,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.testutils.StubTransaction; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.TransitionInfoBuilder; @@ -105,6 +103,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; +import com.android.wm.shell.util.StubTransaction; import org.junit.Before; import org.junit.Test; @@ -703,8 +702,8 @@ public class ShellTransitionTests extends ShellTestCase { createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); final DisplayController displays = createTestDisplayController(); - final @Surface.Rotation int upsideDown = displays - .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation(); + final DisplayLayout displayLayout = displays.getDisplayLayout(DEFAULT_DISPLAY); + final @Surface.Rotation int upsideDown = displayLayout.getUpsideDownRotation(); TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE) .setFlags(FLAG_IS_DISPLAY).setRotate().build(); @@ -744,7 +743,8 @@ public class ShellTransitionTests extends ShellTestCase { assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( displayChange, noTask, displays)); - // Not seamless if one of rotations is upside-down + // Not seamless if the nav bar cares rotation and one of rotations is upside-down. + doReturn(false).when(displayLayout).allowSeamlessRotationDespiteNavBarMoving(); displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build(); final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java new file mode 100644 index 000000000000..855f5416fd0f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2019 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.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.HardwareBuffer; +import android.os.IBinder; +import android.os.Parcel; +import android.view.InputWindowHandle; +import android.view.Surface; +import android.view.SurfaceControl; + +import java.util.HashSet; +import java.util.concurrent.Executor; + +/** + * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit + * testing to avoid calls to native code. + * + * Note: This is a copy of com.android.server.testutils.StubTransaction + */ +public class StubTransaction extends SurfaceControl.Transaction { + + private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>(); + + @Override + public void apply() { + for (Runnable listener : mWindowInfosReportedListeners) { + listener.run(); + } + } + + @Override + public void close() { + } + + @Override + public void apply(boolean sync) { + apply(); + } + + @Override + public SurfaceControl.Transaction setVisibility(SurfaceControl sc, boolean visible) { + return this; + } + + @Override + public SurfaceControl.Transaction show(SurfaceControl sc) { + return this; + } + + @Override + public SurfaceControl.Transaction hide(SurfaceControl sc) { + return this; + } + + @Override + public SurfaceControl.Transaction setPosition(SurfaceControl sc, float x, float y) { + return this; + } + + @Override + public SurfaceControl.Transaction setBufferSize(SurfaceControl sc, + int w, int h) { + return this; + } + + @Override + public SurfaceControl.Transaction setLayer(SurfaceControl sc, int z) { + return this; + } + + @Override + public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, + int z) { + return this; + } + + @Override + public SurfaceControl.Transaction setTransparentRegionHint(SurfaceControl sc, + Region transparentRegion) { + return this; + } + + @Override + public SurfaceControl.Transaction setAlpha(SurfaceControl sc, float alpha) { + return this; + } + + @Override + public SurfaceControl.Transaction setInputWindowInfo(SurfaceControl sc, + InputWindowHandle handle) { + return this; + } + + @Override + public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop, + Rect destFrame, @Surface.Rotation int orientation) { + return this; + } + + @Override + public SurfaceControl.Transaction setMatrix(SurfaceControl sc, + float dsdx, float dtdx, float dtdy, float dsdy) { + return this; + } + + @Override + public SurfaceControl.Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) { + return this; + } + + @Override + public SurfaceControl.Transaction setColorTransform(SurfaceControl sc, float[] matrix, + float[] translation) { + return this; + } + + @Override + public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, Rect crop) { + return this; + } + + @Override + public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, int width, int height) { + return this; + } + + @Override + @NonNull + public SurfaceControl.Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) { + return this; + } + + @Override + public SurfaceControl.Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) { + return this; + } + + @Override + public SurfaceControl.Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) { + return this; + } + + @Override + public SurfaceControl.Transaction setLayerStack(SurfaceControl sc, int layerStack) { + return this; + } + + @Override + public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) { + return this; + } + + @Override + public SurfaceControl.Transaction setColor(SurfaceControl sc, float[] color) { + return this; + } + + @Override + public SurfaceControl.Transaction setSecure(SurfaceControl sc, boolean isSecure) { + return this; + } + + @Override + public SurfaceControl.Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { + return this; + } + + @Override + public SurfaceControl.Transaction setDisplaySurface(IBinder displayToken, Surface surface) { + return this; + } + + @Override + public SurfaceControl.Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) { + return this; + } + + @Override + public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) { + return this; + } + + @Override + public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken, + int orientation, Rect layerStackRect, Rect displayRect) { + return this; + } + + @Override + public SurfaceControl.Transaction setDisplaySize(IBinder displayToken, int width, int height) { + return this; + } + + @Override + public SurfaceControl.Transaction setAnimationTransaction() { + return this; + } + + @Override + public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, int data) { + return this; + } + + @Override + public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, Parcel data) { + return this; + } + + @Override + public SurfaceControl.Transaction merge(SurfaceControl.Transaction other) { + return this; + } + + @Override + public SurfaceControl.Transaction remove(SurfaceControl sc) { + return this; + } + + @Override + public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor, + SurfaceControl.TransactionCommittedListener listener) { + return this; + } + + @Override + public SurfaceControl.Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) { + return this; + } + + @Override + public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc, + int priority) { + return this; + } + + @Override + public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate, + int compatibility, int changeFrameRateStrategy) { + return this; + } + + @Override + public SurfaceControl.Transaction unsetColor(SurfaceControl sc) { + return this; + } + + @Override + public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) { + return this; + } + + @Override + public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc, + @Surface.Rotation int transformHint) { + return this; + } + + @Override + public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) { + return this; + } + + @Override + public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + return this; + } + + @Override + @NonNull + public SurfaceControl.Transaction setBuffer(@NonNull SurfaceControl sc, + @Nullable HardwareBuffer buffer) { + return this; + } + + @Override + public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { + return this; + } + + @Override + public SurfaceControl.Transaction setTrustedOverlay(SurfaceControl sc, + boolean isTrustedOverlay) { + return this; + } + + @Override + public SurfaceControl.Transaction addWindowInfosReportedListener(@NonNull Runnable listener) { + mWindowInfosReportedListeners.add(listener); + return this; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 8f84008e8d2d..3fbab0f9e2bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -55,7 +55,7 @@ class DragDetectorTest { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(eventHandler.handleMotionEvent(any())).thenReturn(true) + `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true) dragDetector = DragDetector(eventHandler) dragDetector.setTouchSlop(SLOP) @@ -72,13 +72,13 @@ class DragDetectorTest { @Test fun testNoMove_passesDownAndUp() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -86,12 +86,12 @@ class DragDetectorTest { @Test fun testMoveInSlop_touch_passesDownAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -99,12 +99,12 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertFalse( dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler, never()).handleMotionEvent(argThat { + verify(eventHandler, never()).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -112,13 +112,13 @@ class DragDetectorTest { @Test fun testMoveInSlop_mouse_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -126,14 +126,14 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -141,25 +141,25 @@ class DragDetectorTest { @Test fun testMoveBeyondSlop_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) val newX = X + SLOP + 1 assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -167,12 +167,12 @@ class DragDetectorTest { @Test fun testPassesHoverEnter() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y }) } @@ -180,7 +180,7 @@ class DragDetectorTest { @Test fun testPassesHoverMove() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y }) } @@ -188,7 +188,7 @@ class DragDetectorTest { @Test fun testPassesHoverExit() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y }) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index f941e9501a3f..7fc1c99bb44e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -57,7 +57,6 @@ import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -411,15 +410,17 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).build(); verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mCaptionMenuWidthId); + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mRelayoutParams.mCaptionHeightId); + windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); - final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int shadowRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuShadowRadiusId); verify(mMockSurfaceControlAddWindowT) .setShadowRadius(additionalWindowSurface, shadowRadius); - final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int cornerRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuCornerRadiusId); verify(mMockSurfaceControlAddWindowT) .setCornerRadius(additionalWindowSurface, cornerRadius); @@ -514,8 +515,7 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { - return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), - mMockDisplayController, mMockShellTaskOrganizer, + return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, taskInfo, testSurface, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp index 0f8a85dd9e62..cec0ee7ee247 100644 --- a/libs/hwui/jni/Gainmap.cpp +++ b/libs/hwui/jni/Gainmap.cpp @@ -86,6 +86,16 @@ jlong Gainmap_createEmpty(JNIEnv*, jobject) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap)); } +jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) { + Gainmap* gainmap = new Gainmap(); + gainmap->incStrong(0); + if (sourcePtr) { + Gainmap* src = fromJava(sourcePtr); + gainmap->info = src->info; + } + return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap)); +} + static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) { android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap); fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap); @@ -237,6 +247,7 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job static const JNINativeMethod gGainmapMethods[] = { {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer}, {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty}, + {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy}, {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap}, {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin}, {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin}, diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 16b35ffcabac..a5518eb9f854 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -405,8 +405,17 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // If the previous frame was dropped we don't need to hold onto it, so // just keep using the previous frame's structure instead - if (!wasSkipped(mCurrentFrameInfo)) { + if (wasSkipped(mCurrentFrameInfo)) { + // Use the oldest skipped frame in case we skip more than a single frame + if (!mSkippedFrameInfo) { + mSkippedFrameInfo.emplace(); + mSkippedFrameInfo->vsyncId = + mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId); + mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); + } + } else { mCurrentFrameInfo = mJankTracker.startFrame(); + mSkippedFrameInfo.reset(); } mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); @@ -602,10 +611,18 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) { const auto inputEventId = static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId)); - native_window_set_frame_timeline_info( - mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId, - mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), - solelyTextureViewUpdates); + const ANativeWindowFrameTimelineInfo ftl = { + .frameNumber = frameCompleteNr, + .frameTimelineVsyncId = vsyncId, + .inputEventId = inputEventId, + .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), + .useForRefreshRateSelection = solelyTextureViewUpdates, + .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId + : UiFrameInfoBuilder::INVALID_VSYNC_ID, + .skippedFrameStartTimeNanos = + mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0, + }; + native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl); } } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 5219b5757008..32ac5af94c14 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -366,6 +366,12 @@ private: ColorMode mColorMode = ColorMode::Default; float mTargetSdrHdrRatio = 1.f; + + struct SkippedFrameInfo { + int64_t vsyncId; + int64_t startTime; + }; + std::optional<SkippedFrameInfo> mSkippedFrameInfo; }; } /* namespace renderthread */ |