diff options
Diffstat (limited to 'libs')
22 files changed, 368 insertions, 124 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index 88fd461debbe..fa35b632a6b3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -16,7 +16,7 @@ package androidx.window.common; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE; @@ -69,14 +69,14 @@ public final class DeviceStateManagerFoldingFeatureProducer * example is activated via public API and can be active in both the "open" and "half folded" * device states. */ - private int mCurrentDeviceState = INVALID_DEVICE_STATE; + private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; /** * Base device state received via * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}. * "Base" in this context means the "physical" state of the device. */ - private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE; + private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; @NonNull private final RawFoldingFeatureProducer mRawFoldSupplier; @@ -177,7 +177,7 @@ public final class DeviceStateManagerFoldingFeatureProducer if (hasListeners()) { mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); } else { - mCurrentDeviceState = INVALID_DEVICE_STATE; + mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index b315f94b5d00..d31bf2a662a3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -16,7 +16,7 @@ package androidx.window.extensions.area; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; import android.app.Activity; import android.content.Context; @@ -79,7 +79,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; @GuardedBy("mLock") - private int mCurrentDeviceState = INVALID_DEVICE_STATE; + private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; @GuardedBy("mLock") private int[] mCurrentSupportedDeviceStates; @@ -143,7 +143,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, mRearDisplayStatusListeners.add(consumer); // If current device state is still invalid, the initial value has not been provided. - if (mCurrentDeviceState == INVALID_DEVICE_STATE) { + if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) { return; } consumer.accept(getCurrentRearDisplayModeStatus()); @@ -308,7 +308,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, mRearDisplayPresentationStatusListeners.add(consumer); // If current device state is still invalid, the initial value has not been provided - if (mCurrentDeviceState == INVALID_DEVICE_STATE) { + if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) { return; } @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus(); @@ -467,7 +467,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @GuardedBy("mLock") private int getCurrentRearDisplayModeStatus() { - if (mRearDisplayState == INVALID_DEVICE_STATE) { + if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { return WindowAreaComponent.STATUS_UNSUPPORTED; } @@ -495,7 +495,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @GuardedBy("mLock") private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) { - if (mRearDisplayState == INVALID_DEVICE_STATE) { + if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { return; } synchronized (mLock) { @@ -507,7 +507,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @GuardedBy("mLock") private int getCurrentRearDisplayPresentationModeStatus() { - if (mConcurrentDisplayState == INVALID_DEVICE_STATE) { + if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { return WindowAreaComponent.STATUS_UNSUPPORTED; } @@ -530,7 +530,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @GuardedBy("mLock") private void updateRearDisplayPresentationStatusListeners( @WindowAreaStatus int windowAreaStatus) { - if (mConcurrentDisplayState == INVALID_DEVICE_STATE) { + if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { return; } RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus( diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index a883e087cb3b..b54f9cf2f15d 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -109,7 +109,7 @@ <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string> <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string> <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string> - <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string> + <string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string> <string name="more_button_text" msgid="3655388105592893530">"Plus"</string> <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string> <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index a541c590575f..c68b0be47228 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -148,4 +148,7 @@ <!-- Whether pointer pilfer is required to start back animation. --> <bool name="config_backAnimationRequiresPointerPilfer">true</bool> + + <!-- Whether desktop mode is supported on the current device --> + <bool name="config_isDesktopModeSupported">false</bool> </resources> 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 474430eb44ab..23bdd08e6b24 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 @@ -2474,11 +2474,12 @@ public class BubbleStackView extends FrameLayout // Let the expanded animation controller know that it shouldn't animate child adds/reorders // since we're about to animate collapsed. mExpandedAnimationController.notifyPreparingToCollapse(); - + final PointF collapsePosition = mStackAnimationController + .getStackPositionAlongNearestHorizontalEdge(); updateOverflowDotVisibility(false /* expanding */); final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack( - mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(), + collapsePosition, /* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded, () -> { mBubbleContainer.setActiveController(mStackAnimationController); @@ -2501,7 +2502,8 @@ public class BubbleStackView extends FrameLayout } mExpandedViewAnimationController.reset(); }; - mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after); + mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after, + collapsePosition); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { // When the animation completes, we should no longer be showing the content. // This won't actually update content visibility immediately, if we are currently diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java index 8a33780bc8d5..41755293f382 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java @@ -15,6 +15,8 @@ */ package com.android.wm.shell.bubbles.animation; +import android.graphics.PointF; + import com.android.wm.shell.bubbles.BubbleExpandedView; /** @@ -55,8 +57,9 @@ public interface ExpandedViewAnimationController { * @param startStackCollapse runnable that is triggered when bubbles can start moving back to * their collapsed location * @param after runnable to run after animation is complete + * @param collapsePosition the position on screen the stack will collapse to */ - void animateCollapse(Runnable startStackCollapse, Runnable after); + void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition); /** * Animate the view back to fully expanded state. @@ -69,6 +72,22 @@ public interface ExpandedViewAnimationController { void animateForImeVisibilityChange(boolean visible); /** + * Whether this controller should also animate the expansion for the bubble + */ + boolean shouldAnimateExpansion(); + + /** + * Animate the expansion of the bubble. + * + * @param startDelayMillis how long to delay starting the expansion animation + * @param after runnable to run after the animation is complete + * @param collapsePosition the position on screen the stack will collapse to (and expand from) + * @param bubblePosition the position of the bubble on screen that the view is associated with + */ + void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition, + PointF bubblePosition); + + /** * Reset the view to fully expanded state */ void reset(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java index e43609fe8ff0..aa4129a14dbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -28,6 +28,7 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.PointF; import android.view.HapticFeedbackConstants; import android.view.ViewConfiguration; @@ -187,9 +188,11 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio } @Override - public void animateCollapse(Runnable startStackCollapse, Runnable after) { - ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d", - mSwipeUpVelocity, mMinFlingVelocity); + public void animateCollapse(Runnable startStackCollapse, Runnable after, + PointF collapsePosition) { + ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d" + + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity, + collapsePosition.x, collapsePosition.y); if (mExpandedView != null) { // Mark it as animating immediately to avoid updates to the view before animation starts mExpandedView.setAnimating(true); @@ -274,6 +277,17 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio } @Override + public boolean shouldAnimateExpansion() { + return false; + } + + @Override + public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition, + PointF bubblePosition) { + // TODO - animate + } + + @Override public void reset() { ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state"); if (mExpandedView == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index 4c0281dcc517..e261d92bda5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -16,6 +16,8 @@ package com.android.wm.shell.common; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL; + import android.annotation.BinderThread; import android.annotation.NonNull; import android.os.RemoteException; @@ -26,6 +28,7 @@ import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.transition.LegacyTransitions; import java.util.ArrayList; @@ -204,6 +207,7 @@ public final class SyncTransactionQueue { @Override public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) { + ProtoLog.v(WM_SHELL, "SyncTransactionQueue.onTransactionReady(): syncId=%d", id); mMainExecutor.execute(() -> { synchronized (mQueue) { if (mId != id) { @@ -223,6 +227,8 @@ public final class SyncTransactionQueue { Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e); } } else { + ProtoLog.v(WM_SHELL, + "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id); t.apply(); t.close(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 7b8486870a40..494d89307514 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -16,13 +16,13 @@ package com.android.wm.shell.desktopmode; -import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE; - import android.annotation.NonNull; -import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; import android.os.SystemProperties; +import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; +import com.android.wm.shell.R; /** * Constants for desktop mode feature @@ -70,8 +70,11 @@ public class DesktopModeStatus { private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean( "persist.wm.debug.desktop_use_rounded_corners", true); - private static final boolean ENFORCE_DISPLAY_RESTRICTIONS = SystemProperties.getBoolean( - "persist.wm.debug.desktop_mode_enforce_display_restrictions", true); + /** + * Flag to indicate whether to restrict desktop mode to supported devices. + */ + private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean( + "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); /** * Return {@code true} if desktop windowing is enabled @@ -113,19 +116,25 @@ public class DesktopModeStatus { } /** - * Return whether the display size restrictions should be enforced. + * Return {@code true} if desktop mode should be restricted to supported devices. + */ + @VisibleForTesting + public static boolean enforceDeviceRestrictions() { + return ENFORCE_DEVICE_RESTRICTIONS; + } + + /** + * Return {@code true} if the current device supports desktop mode. */ - public static boolean enforceDisplayRestrictions() { - return ENFORCE_DISPLAY_RESTRICTIONS; + @VisibleForTesting + public static boolean isDesktopModeSupported(@NonNull Context context) { + return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported); } /** - * Return {@code true} if the display associated with the task is at least of size - * {@link android.content.res.Configuration#SCREENLAYOUT_SIZE_XLARGE} or has been overridden to - * ignore the size constraint. + * Return {@code true} if desktop mode can be entered on the current device. */ - public static boolean meetsMinimumDisplayRequirements(@NonNull RunningTaskInfo taskInfo) { - return !enforceDisplayRestrictions() - || taskInfo.configuration.isLayoutSizeAtLeast(SCREENLAYOUT_SIZE_XLARGE); + public static boolean canEnterDesktopMode(@NonNull Context context) { + return !enforceDeviceRestrictions() || isDesktopModeSupported(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 c2c944211552..95237c38f309 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 @@ -306,7 +306,7 @@ class DesktopTasksController( task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { - if (!DesktopModeStatus.meetsMinimumDisplayRequirements(task)) { + if (!DesktopModeStatus.canEnterDesktopMode(context)) { KtProtoLog.w( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " + "display does not meet minimum size requirements") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index e73a85003881..4c69cc3cc61d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -32,13 +32,16 @@ import android.view.SurfaceControl; import androidx.annotation.BinderThread; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.pip.IPip; import com.android.wm.shell.common.pip.IPipAnimationListener; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -57,15 +60,40 @@ public class PipController implements ConfigurationChangeListener, DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> { private static final String TAG = PipController.class.getSimpleName(); - private Context mContext; - private ShellController mShellController; - private DisplayController mDisplayController; - private DisplayInsetsController mDisplayInsetsController; - private PipBoundsState mPipBoundsState; - private PipBoundsAlgorithm mPipBoundsAlgorithm; - private PipDisplayLayoutState mPipDisplayLayoutState; - private PipScheduler mPipScheduler; - private ShellExecutor mMainExecutor; + private final Context mContext; + private final ShellController mShellController; + private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; + private final PipBoundsState mPipBoundsState; + private final PipBoundsAlgorithm mPipBoundsAlgorithm; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final PipScheduler mPipScheduler; + private final ShellExecutor mMainExecutor; + + // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents. + private PipAnimationListener mPipRecentsAnimationListener; + + @VisibleForTesting + interface PipAnimationListener { + /** + * Notifies the listener that the Pip animation is started. + */ + void onPipAnimationStarted(); + + /** + * Notifies the listener about PiP resource dimensions changed. + * Listener can expect an immediate callback the first time they attach. + * + * @param cornerRadius the pixel value of the corner radius, zero means it's disabled. + * @param shadowRadius the pixel value of the shadow radius, zero means it's disabled. + */ + void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius); + + /** + * Notifies the listener that user leaves PiP by tapping on the expand button. + */ + void onExpandPip(); + } private PipController(Context context, ShellInit shellInit, @@ -92,14 +120,27 @@ public class PipController implements ConfigurationChangeListener, } } - @Override - public Context getContext() { - return mContext; - } - - @Override - public ShellExecutor getRemoteCallExecutor() { - return mMainExecutor; + /** + * Instantiates {@link PipController}, returns {@code null} if the feature not supported. + */ + public static PipController create(Context context, + ShellInit shellInit, + ShellController shellController, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipDisplayLayoutState pipDisplayLayoutState, + PipScheduler pipScheduler, + ShellExecutor mainExecutor) { + if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Device doesn't support Pip feature", TAG); + return null; + } + return new PipController(context, shellInit, shellController, displayController, + displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, + pipScheduler, mainExecutor); } private void onInit() { @@ -109,7 +150,6 @@ public class PipController implements ConfigurationChangeListener, DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay()); mPipDisplayLayoutState.setDisplayLayout(layout); - mShellController.addConfigurationChangeListener(this); mDisplayController.addDisplayWindowListener(this); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new DisplayInsetsController.OnInsetsChangedListener() { @@ -123,45 +163,50 @@ public class PipController implements ConfigurationChangeListener, // Allow other outside processes to bind to PiP controller using the key below. mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP, this::createExternalInterface, this); - } - - /** - * Instantiates {@link PipController}, returns {@code null} if the feature not supported. - */ - public static PipController create(Context context, - ShellInit shellInit, - ShellController shellController, - DisplayController displayController, - DisplayInsetsController displayInsetsController, - PipBoundsState pipBoundsState, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipDisplayLayoutState pipDisplayLayoutState, - PipScheduler pipScheduler, - ShellExecutor mainExecutor) { - if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Device doesn't support Pip feature", TAG); - return null; - } - return new PipController(context, shellInit, shellController, displayController, - displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, - pipScheduler, mainExecutor); + mShellController.addConfigurationChangeListener(this); } private ExternalInterfaceBinder createExternalInterface() { return new IPipImpl(this); } + // + // RemoteCallable implementations + // + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + // + // ConfigurationChangeListener implementations + // + @Override public void onConfigurationChanged(Configuration newConfiguration) { mPipDisplayLayoutState.onConfigurationChanged(); } @Override + public void onDensityOrFontScaleChanged() { + onPipResourceDimensionsChanged(); + } + + @Override public void onThemeChanged() { onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay())); } + // + // DisplayController.OnDisplaysChangedListener implementations + // + @Override public void onDisplayAdded(int displayId) { if (displayId != mPipDisplayLayoutState.getDisplayId()) { @@ -182,6 +227,10 @@ public class PipController implements ConfigurationChangeListener, mPipDisplayLayoutState.setDisplayLayout(layout); } + // + // IPip Binder stub helpers + // + private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams pictureInPictureParams, int launcherRotation, Rect hotseatKeepClearArea) { @@ -197,18 +246,56 @@ public class PipController implements ConfigurationChangeListener, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onSwipePipToHomeAnimationStart: %s", componentName); mPipScheduler.setInSwipePipToHomeTransition(true); + mPipRecentsAnimationListener.onPipAnimationStarted(); // TODO: cache the overlay if provided for reparenting later. } + // + // IPipAnimationListener Binder proxy helpers + // + + private void setPipRecentsAnimationListener(PipAnimationListener pipAnimationListener) { + mPipRecentsAnimationListener = pipAnimationListener; + onPipResourceDimensionsChanged(); + } + + private void onPipResourceDimensionsChanged() { + if (mPipRecentsAnimationListener != null) { + mPipRecentsAnimationListener.onPipResourceDimensionsChanged( + mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius), + mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius)); + } + } + /** * The interface for calls from outside the host process. */ @BinderThread private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder { private PipController mController; + private final SingleInstanceRemoteListener<PipController, IPipAnimationListener> mListener; + private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() { + @Override + public void onPipAnimationStarted() { + mListener.call(l -> l.onPipAnimationStarted()); + } + + @Override + public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) { + mListener.call(l -> l.onPipResourceDimensionsChanged(cornerRadius, shadowRadius)); + } + + @Override + public void onExpandPip() { + mListener.call(l -> l.onExpandPip()); + } + }; IPipImpl(PipController controller) { mController = controller; + mListener = new SingleInstanceRemoteListener<>(mController, + (cntrl) -> cntrl.setPipRecentsAnimationListener(mPipAnimationListener), + (cntrl) -> cntrl.setPipRecentsAnimationListener(null)); } /** @@ -217,6 +304,7 @@ public class PipController implements ConfigurationChangeListener, @Override public void invalidate() { mController = null; + mListener.unregister(); } @Override @@ -257,7 +345,14 @@ public class PipController implements ConfigurationChangeListener, @Override public void setPipAnimationListener(IPipAnimationListener listener) { - // TODO: set a proper animation listener to update the Launcher state as needed. + executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener", + (controller) -> { + if (listener != null) { + mListener.register(listener); + } else { + mListener.unregister(); + } + }); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 895c793007a5..6665013aa68d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -172,7 +172,7 @@ public class PipScheduler { } void setInSwipePipToHomeTransition(boolean inSwipePipToHome) { - mInSwipePipToHomeTransition = true; + mInSwipePipToHomeTransition = inSwipePipToHome; } boolean isInSwipePipToHomeTransition() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index dfb04758c851..d15da4a43db4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -226,7 +226,26 @@ public class PipTransition extends PipTransitionController { // cache the PiP task token and leash mPipScheduler.setPipTaskToken(mPipTaskToken); + SurfaceControl pipLeash = pipChange.getLeash(); + + PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; + Rect srcRectHint = params.getSourceRectHint(); + Rect destinationBounds = pipChange.getEndAbsBounds(); + if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) { + float scale = (float) destinationBounds.width() / srcRectHint.width(); + startTransaction.setWindowCrop(pipLeash, srcRectHint); + startTransaction.setPosition(pipLeash, + destinationBounds.left - srcRectHint.left * scale, + destinationBounds.top - srcRectHint.top * scale); + + // Reset the scale in case we are in the multi-activity case. + // TO_FRONT transition already scales down the task in single-activity case, but + // in multi-activity case, reparenting yields new reset scales coming from pinned task. + startTransaction.setScale(pipLeash, scale, scale); + } else { + // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay). + } startTransaction.apply(); finishCallback.onTransitionFinished(null); return true; @@ -303,6 +322,7 @@ public class PipTransition extends PipTransitionController { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); + wct.deferConfigToTransitionEnd(pipTask.token); return wct; } 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 b5ea1b1b43ea..235456c0f716 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 @@ -418,6 +418,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.start", mInstanceId); if (mListener == null || mTransition == null) { + Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) + + " hasTransition=" + (mTransition != null)); cleanUp(); return false; } @@ -531,21 +533,31 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { // Put into the "below" layer space. t.setLayer(change.getLeash(), layer); mOpeningTasks.add(new TaskState(change, null /* leash */)); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " unhandled root taskId=%d", taskInfo.taskId); } } else if (TransitionUtil.isDividerBar(change)) { final RemoteAnimationTarget target = TransitionUtil.newTarget(change, belowLayers - i, info, t, mLeashMap); // Add this as a app and we will separate them on launcher side by window type. apps.add(target); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " unhandled change taskId=%d", + taskInfo != null ? taskInfo.taskId : -1); } } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "Applying transaction=%d", t.getId()); t.apply(); Bundle b = new Bundle(1 /*capacity*/); b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId)); try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.start: calling onAnimationStart", mInstanceId); + "[%d] RecentsController.start: calling onAnimationStart with %d apps", + mInstanceId, apps.size()); mListener.onAnimationStart(this, apps.toArray(new RemoteAnimationTarget[apps.size()]), wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index e2be1533118a..1ce87ef73fd7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -22,6 +22,7 @@ import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL; import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION; import android.annotation.CallSuper; +import android.annotation.NonNull; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.Context; @@ -306,7 +307,7 @@ public class StartingSurfaceDrawer { @CallSuper protected void removeImmediately() { mRemoveExecutor.removeCallbacks(mScheduledRunnable); - mRecordManager.onRecordRemoved(mTaskId); + mRecordManager.onRecordRemoved(this, mTaskId); } } @@ -327,6 +328,11 @@ public class StartingSurfaceDrawer { } void addRecord(int taskId, StartingWindowRecord record) { + final StartingWindowRecord original = mStartingWindowRecords.get(taskId); + if (original != null) { + mTmpRemovalInfo.taskId = taskId; + original.removeIfPossible(mTmpRemovalInfo, true /* immediately */); + } mStartingWindowRecords.put(taskId, record); } @@ -346,8 +352,11 @@ public class StartingSurfaceDrawer { removeWindow(mTmpRemovalInfo, true/* immediately */); } - void onRecordRemoved(int taskId) { - mStartingWindowRecords.remove(taskId); + void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) { + final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId); + if (currentRecord == record) { + mStartingWindowRecords.remove(taskId); + } } StartingWindowRecord getRecord(int taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java index 61e11e877b90..89b0e25b306b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java @@ -16,6 +16,8 @@ package com.android.wm.shell.transition; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; + import android.annotation.NonNull; import android.os.RemoteException; import android.view.IRemoteAnimationFinishedCallback; @@ -26,6 +28,8 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IWindowContainerTransactionCallback; +import com.android.internal.protolog.common.ProtoLog; + /** * Utilities and interfaces for transition-like usage on top of the legacy app-transition and * synctransaction tools. @@ -87,9 +91,11 @@ public class LegacyTransitions { @Override public void onTransactionReady(int id, SurfaceControl.Transaction t) throws RemoteException { + ProtoLog.v(WM_SHELL_TRANSITIONS, + "LegacyTransitions.onTransactionReady(): syncId=%d", id); mSyncId = id; mTransaction = t; - checkApply(); + checkApply(true /* log */); } } @@ -103,20 +109,29 @@ public class LegacyTransitions { mWallpapers = wallpapers; mNonApps = nonApps; mFinishCallback = finishedCallback; - checkApply(); + checkApply(false /* log */); } @Override public void onAnimationCancelled() throws RemoteException { mCancelled = true; mApps = mWallpapers = mNonApps = null; - checkApply(); + checkApply(false /* log */); } } - private void checkApply() throws RemoteException { - if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) return; + private void checkApply(boolean log) throws RemoteException { + if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) { + if (log) { + ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b", + mFinishCallback != null, mCancelled); + } + return; + } + if (log) { + ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply"); + } mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers, mNonApps, mFinishCallback, mTransaction); } 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 b8a0f6703b97..ccd0b2df8cf1 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 @@ -1409,6 +1409,8 @@ public class Transitions implements RemoteCallable<Transitions>, public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) throws RemoteException { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)", + t.getId()); mMainExecutor.execute(() -> Transitions.this.onTransitionReady( iBinder, transitionInfo, t, finishT)); } 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 bf22193566ed..98ff0eed9c11 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 @@ -866,6 +866,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } if (mTransitionDragActive) { + // Do not create an indicator at all if we're not past transition height. + if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android + .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height) + && mMoveToDesktopAnimator == null) { + return; + } final DesktopModeVisualIndicator.IndicatorType indicatorType = mDesktopTasksController.updateVisualIndicator( relevantDecor.mTaskInfo, @@ -1052,7 +1058,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop() - && DesktopModeStatus.meetsMinimumDisplayRequirements(taskInfo); + && DesktopModeStatus.canEnterDesktopMode(mContext); } private void createWindowDecoration( 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 92b187f90d65..0136751d8c9a 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 @@ -23,8 +23,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED -import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL -import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE import android.os.Binder import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -38,6 +36,7 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession @@ -89,6 +88,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.quality.Strictness @SmallTest @RunWith(AndroidTestingRunner::class) @@ -126,7 +126,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Before fun setUp() { - mockitoSession = mockitoSession().spyStatic(DesktopModeStatus::class.java).startMocking() + mockitoSession = mockitoSession().strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java).startMocking() whenever(DesktopModeStatus.isEnabled()).thenReturn(true) shellInit = Mockito.spy(ShellInit(testExecutor)) @@ -335,25 +336,25 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_screenSizeBelowXLarge_doesNothing() { + fun moveToDesktop_deviceNotSupported_doesNothing() { val task = setUpFullscreenTask() - // Update screen layout to be below minimum size - task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + // Simulate non compatible device + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } controller.moveToDesktop(task) verifyWCTNotExecuted() } @Test - fun moveToDesktop_screenSizeBelowXLarge_displayRestrictionsOverridden_taskIsMovedToDesktop() { + fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { val task = setUpFullscreenTask() - // Update screen layout to be below minimum size - task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + // Simulate non compatible device + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - // Simulate enforce display restrictions system property overridden to false - whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false) + // Simulate enforce device restrictions system property overridden to false + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false) controller.moveToDesktop(task) @@ -363,7 +364,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_screenSizeXLarge_taskIsMovedToDesktop() { + fun moveToDesktop_deviceSupported_taskIsMovedToDesktop() { val task = setUpFullscreenTask() controller.moveToDesktop(task) @@ -874,7 +875,8 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFullscreenTask(displayId) - task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task @@ -882,7 +884,8 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) - task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 83519bbf624a..6940739d68b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -23,8 +23,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.Context -import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL -import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE import android.graphics.Rect import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay @@ -47,6 +45,7 @@ import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -367,30 +366,41 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun testWindowDecor_screenSizeBelowXLarge_decorNotCreated() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) - // Update screen layout to be below minimum size - task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() { + val mockitoSession: StaticMockitoSession = mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + try { + // Simulate default enforce device restrictions system property + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) - onTaskOpening(task) - verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + // Simulate device that doesn't support desktop mode + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory, never()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } finally { + mockitoSession.finishMocking() + } } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun testWindowDecor_screenSizeBelowXLarge_displayRestrictionsOverridden_decorCreated() { + fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() { val mockitoSession: StaticMockitoSession = mockitoSession() .strictness(Strictness.LENIENT) .spyStatic(DesktopModeStatus::class.java) .startMocking() try { - // Simulate enforce display restrictions system property overridden to false - whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false) + // Simulate enforce device restrictions system property overridden to false + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false) + // Simulate device that doesn't support desktop mode + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) - // Update screen layout to be below minimum size - task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL setUpMockDecorationsForTasks(task) onTaskOpening(task) @@ -403,14 +413,25 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun testWindowDecor_screenSizeXLarge_decorCreated() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) - task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE - setUpMockDecorationsForTasks(task) + fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() { + val mockitoSession: StaticMockitoSession = mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + try { + // Simulate default enforce device restrictions system property + whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) - onTaskOpening(task) - verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + setUpMockDecorationsForTasks(task) + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } finally { + mockitoSession.finishMocking() + } } private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 3d7e559bebe0..76a0a6499d33 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -76,3 +76,10 @@ flag { description: "Automatically animate all changes in HDR headroom" bug: "314810174" } + +flag { + name: "draw_region" + namespace: "core_graphics" + description: "Add canvas#drawRegion API" + bug: "318612129" +} diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 1d0330185b1c..abf64d099935 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -411,7 +411,8 @@ 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 (const auto reason = wasSkipped(mCurrentFrameInfo)) { + const auto reason = wasSkipped(mCurrentFrameInfo); + if (reason.has_value()) { // Use the oldest skipped frame in case we skip more than a single frame if (!mSkippedFrameInfo) { switch (*reason) { |