diff options
Diffstat (limited to 'libs')
42 files changed, 946 insertions, 325 deletions
diff --git a/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml b/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml new file mode 100644 index 000000000000..975d25b25953 --- /dev/null +++ b/libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:alpha="0.35" android:color="@androidprv:color/materialColorPrimaryContainer" /> +</selector> diff --git a/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml new file mode 100644 index 000000000000..89546f9b0807 --- /dev/null +++ b/libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="28dp" /> + <solid android:color="@color/bubble_drop_target_background_color" /> + <stroke + android:width="1dp" + android:color="@androidprv:color/materialColorPrimaryContainer" /> +</shape> diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml index d280083ae7f5..5f013c52d70d 100644 --- a/libs/WindowManager/Shell/shared/res/values/dimen.xml +++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml @@ -36,4 +36,13 @@ <dimen name="drag_zone_v_split_from_expanded_view_height_tablet">285dp</dimen> <dimen name="drag_zone_v_split_from_expanded_view_height_fold_tall">150dp</dimen> <dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen> + + <!-- Bubble drop target dimensions --> + <dimen name="drop_target_full_screen_padding">20dp</dimen> + <dimen name="drop_target_desktop_window_padding_small">100dp</dimen> + <dimen name="drop_target_desktop_window_padding_large">130dp</dimen> + <dimen name="drop_target_expanded_view_width">364</dimen> + <dimen name="drop_target_expanded_view_height">578</dimen> + <dimen name="drop_target_expanded_view_padding_bottom">108</dimen> + <dimen name="drop_target_expanded_view_padding_horizontal">24</dimen> </resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt index 481fc7fcb869..6acd9dbe8b91 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt @@ -70,7 +70,8 @@ enum class BubbleBarLocation : Parcelable { UpdateSource.A11Y_ACTION_BAR, UpdateSource.A11Y_ACTION_BUBBLE, UpdateSource.A11Y_ACTION_EXP_VIEW, - UpdateSource.APP_ICON_DRAG + UpdateSource.APP_ICON_DRAG, + UpdateSource.DRAG_TASK, ) @Retention(AnnotationRetention.SOURCE) annotation class UpdateSource { @@ -95,6 +96,9 @@ enum class BubbleBarLocation : Parcelable { /** Location changed from dragging the application icon to the bubble bar */ const val APP_ICON_DRAG = 7 + + /** Location changed from dragging a running task to the bubble bar */ + const val DRAG_TASK = 8 } } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt index 909e9d2c4428..1a80b0f29aa9 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.graphics.Rect +import android.util.TypedValue import androidx.annotation.DimenRes import com.android.wm.shell.shared.R import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode @@ -50,6 +51,60 @@ class DragZoneFactory( private var vSplitFromExpandedViewDragZoneHeightFoldTall = 0 private var vSplitFromExpandedViewDragZoneHeightFoldShort = 0 + private var fullScreenDropTargetPadding = 0 + private var desktopWindowDropTargetPaddingSmall = 0 + private var desktopWindowDropTargetPaddingLarge = 0 + private var expandedViewDropTargetWidth = 0 + private var expandedViewDropTargetHeight = 0 + private var expandedViewDropTargetPaddingBottom = 0 + private var expandedViewDropTargetPaddingHorizontal = 0 + + private val fullScreenDropTarget: Rect + get() = + Rect(windowBounds).apply { + inset(fullScreenDropTargetPadding, fullScreenDropTargetPadding) + } + + private val desktopWindowDropTarget: Rect + get() = + Rect(windowBounds).apply { + if (deviceConfig.isLandscape) { + inset( + /* dx= */ desktopWindowDropTargetPaddingLarge, + /* dy= */ desktopWindowDropTargetPaddingSmall + ) + } else { + inset( + /* dx= */ desktopWindowDropTargetPaddingSmall, + /* dy= */ desktopWindowDropTargetPaddingLarge + ) + } + } + + private val expandedViewDropTargetLeft: Rect + get() = + Rect( + expandedViewDropTargetPaddingHorizontal, + windowBounds.bottom - + expandedViewDropTargetPaddingBottom - + expandedViewDropTargetHeight, + expandedViewDropTargetWidth + expandedViewDropTargetPaddingHorizontal, + windowBounds.bottom - expandedViewDropTargetPaddingBottom + ) + + private val expandedViewDropTargetRight: Rect + get() = + Rect( + windowBounds.right - + expandedViewDropTargetPaddingHorizontal - + expandedViewDropTargetWidth, + windowBounds.bottom - + expandedViewDropTargetPaddingBottom - + expandedViewDropTargetHeight, + windowBounds.right - expandedViewDropTargetPaddingHorizontal, + windowBounds.bottom - expandedViewDropTargetPaddingBottom + ) + init { onConfigurationUpdated() } @@ -88,11 +143,32 @@ class DragZoneFactory( context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_tall) vSplitFromExpandedViewDragZoneHeightFoldShort = context.resolveDimension(R.dimen.drag_zone_v_split_from_expanded_view_height_fold_short) + fullScreenDropTargetPadding = + context.resolveDimension(R.dimen.drop_target_full_screen_padding) + desktopWindowDropTargetPaddingSmall = + context.resolveDimension(R.dimen.drop_target_desktop_window_padding_small) + desktopWindowDropTargetPaddingLarge = + context.resolveDimension(R.dimen.drop_target_desktop_window_padding_large) + + // TODO b/393172431: Use the shared xml resources once we can easily access them from + // launcher + expandedViewDropTargetWidth = 364.dpToPx() + expandedViewDropTargetHeight = 578.dpToPx() + expandedViewDropTargetPaddingBottom = 108.dpToPx() + expandedViewDropTargetPaddingHorizontal = 24.dpToPx() } private fun Context.resolveDimension(@DimenRes dimension: Int) = resources.getDimensionPixelSize(dimension) + private fun Int.dpToPx() = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this.toFloat(), + context.resources.displayMetrics + ) + .toInt() + /** * Creates the list of drag zones for the dragged object. * @@ -155,7 +231,7 @@ class DragZoneFactory( DragZone.Bubble.Left( bounds = Rect(0, windowBounds.bottom - dragZoneSize, dragZoneSize, windowBounds.bottom), - dropTarget = Rect(0, 0, 0, 0), + dropTarget = expandedViewDropTargetLeft, ), DragZone.Bubble.Right( bounds = @@ -165,7 +241,7 @@ class DragZoneFactory( windowBounds.right, windowBounds.bottom, ), - dropTarget = Rect(0, 0, 0, 0), + dropTarget = expandedViewDropTargetRight, ) ) } @@ -174,7 +250,7 @@ class DragZoneFactory( return listOf( DragZone.Bubble.Left( bounds = Rect(0, 0, windowBounds.right / 2, windowBounds.bottom), - dropTarget = Rect(0, 0, 0, 0), + dropTarget = expandedViewDropTargetLeft, ), DragZone.Bubble.Right( bounds = @@ -184,7 +260,7 @@ class DragZoneFactory( windowBounds.right, windowBounds.bottom, ), - dropTarget = Rect(0, 0, 0, 0), + dropTarget = expandedViewDropTargetRight, ) ) } @@ -198,7 +274,7 @@ class DragZoneFactory( windowBounds.right / 2 + fullScreenDragZoneWidth / 2, fullScreenDragZoneHeight ), - dropTarget = Rect(0, 0, 0, 0) + dropTarget = fullScreenDropTarget ) } @@ -223,7 +299,7 @@ class DragZoneFactory( windowBounds.bottom / 2 + desktopWindowDragZoneHeight / 2 ) }, - dropTarget = Rect(0, 0, 0, 0) + dropTarget = desktopWindowDropTarget ) } @@ -236,7 +312,7 @@ class DragZoneFactory( windowBounds.right / 2 + desktopWindowFromExpandedViewDragZoneWidth / 2, windowBounds.bottom / 2 + desktopWindowFromExpandedViewDragZoneHeight / 2 ), - dropTarget = Rect(0, 0, 0, 0) + dropTarget = desktopWindowDropTarget ) } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 643c1506e4c2..00901a4d980d 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -226,6 +226,7 @@ public class DesktopModeStatus { return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); } + /** * Return {@code true} if desktop mode dev option should be shown on current device */ @@ -239,23 +240,22 @@ public class DesktopModeStatus { */ public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) { return Flags.showDesktopExperienceDevOption() - && isInternalDisplayEligibleToHostDesktops(context); + && isDeviceEligibleForDesktopMode(context); } /** Returns if desktop mode dev option should be enabled if there is no user override. */ public static boolean shouldDevOptionBeEnabledByDefault(Context context) { - return isInternalDisplayEligibleToHostDesktops(context) - && Flags.enableDesktopWindowingMode(); + return isDeviceEligibleForDesktopMode(context) + && Flags.enableDesktopWindowingMode(); } /** * Return {@code true} if desktop mode is enabled and can be entered on the current device. */ public static boolean canEnterDesktopMode(@NonNull Context context) { - return (isInternalDisplayEligibleToHostDesktops(context) - && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue() - && (isDesktopModeSupported(context) || !enforceDeviceRestrictions()) - || isDesktopModeEnabledByDevOption(context)); + return (isDeviceEligibleForDesktopMode(context) + && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()) + || isDesktopModeEnabledByDevOption(context); } /** @@ -323,25 +323,34 @@ public class DesktopModeStatus { } /** - * Return {@code true} if desktop sessions is unrestricted and can be host for the device's - * internal display. + * Return {@code true} if desktop mode is unrestricted and is supported on the device. */ - public static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) { - return !enforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || ( - Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported( - context)); + public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { + if (!enforceDeviceRestrictions()) { + return true; + } + final boolean desktopModeSupported = isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context); + final boolean desktopModeSupportedByDevOptions = + Flags.enableDesktopModeThroughDevOption() + && isDesktopModeDevOptionSupported(context); + return desktopModeSupported || desktopModeSupportedByDevOptions; } /** * Return {@code true} if the developer option for desktop mode is unrestricted and is supported * in the device. * - * Note that, if {@link #isInternalDisplayEligibleToHostDesktops(Context)} is true, then + * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then * {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true. */ private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) { - return !enforceDeviceRestrictions() || isDesktopModeSupported(context) - || isDesktopModeDevOptionSupported(context); + if (!enforceDeviceRestrictions()) { + return true; + } + final boolean desktopModeSupported = isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context); + return desktopModeSupported || isDesktopModeDevOptionSupported(context); } /** 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 a780fb7a426e..58b46d202599 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 @@ -845,6 +845,10 @@ public class BubbleController implements ConfigurationChangeListener, mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_APP_ICON_DROP : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_APP_ICON_DROP); break; + case BubbleBarLocation.UpdateSource.DRAG_TASK: + mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_TASK + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK); + break; } } @@ -1603,13 +1607,21 @@ public class BubbleController implements ConfigurationChangeListener, if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return; Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId); + BubbleBarLocation location = null; + if (dragData != null) { + location = + dragData.isReleasedOnLeft() ? BubbleBarLocation.LEFT : BubbleBarLocation.RIGHT; + } if (b.isInflated()) { - mBubbleData.setSelectedBubbleAndExpandStack(b); + mBubbleData.setSelectedBubbleAndExpandStack(b, location); if (dragData != null && dragData.getPendingWct() != null) { mTransitions.startTransition(TRANSIT_CHANGE, dragData.getPendingWct(), /* handler= */ null); } } else { + if (location != null) { + setBubbleBarLocation(location, BubbleBarLocation.UpdateSource.DRAG_TASK); + } b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); // Lazy init stack view when a bubble is created ensureBubbleViewsAndWindowCreated(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java index 831f2271d500..a0c473173bf1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java @@ -156,6 +156,12 @@ public class BubbleLogger { @UiEvent(doc = "while bubble bar is expanded, switch to another/existing bubble") BUBBLE_BAR_BUBBLE_SWITCHED(1977), + @UiEvent(doc = "bubble bar moved to the left edge of the screen by dragging a task") + BUBBLE_BAR_MOVED_LEFT_DRAG_TASK(2146), + + @UiEvent(doc = "bubble bar moved to the right edge of the screen by dragging a task") + BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK(2147), + // endregion ; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java index 6be3c1f18b39..a676f41baafe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -156,14 +156,18 @@ public class BubbleTransitions { public static class DragData { private final Rect mBounds; private final WindowContainerTransaction mPendingWct; + private final boolean mReleasedOnLeft; /** * @param bounds bounds of the dragged task when the drag was released * @param wct pending operations to be applied when finishing the drag + * @param releasedOnLeft true if the bubble was released in the left drop target */ - public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct) { + public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct, + boolean releasedOnLeft) { mBounds = bounds; mPendingWct = wct; + mReleasedOnLeft = releasedOnLeft; } /** @@ -181,6 +185,13 @@ public class BubbleTransitions { public WindowContainerTransaction getPendingWct() { return mPendingWct; } + + /** + * @return true if the bubble was released in the left drop target + */ + public boolean isReleasedOnLeft() { + return mReleasedOnLeft; + } } /** 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 35475c7ee4ce..2fd8c27d5970 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 @@ -759,7 +759,6 @@ public abstract class WMShellModule { FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, - DesktopTilingDecorViewModel desktopTilingDecorViewModel, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, Optional<BubbleController> bubbleController, OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver, @@ -798,7 +797,6 @@ public abstract class WMShellModule { mainHandler, desktopModeEventLogger, desktopModeUiEventLogger, - desktopTilingDecorViewModel, desktopWallpaperActivityTokenProvider, bubbleController, overviewToDesktopTransitionObserver, @@ -990,7 +988,8 @@ public abstract class WMShellModule { DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, - DesktopModeCompatPolicy desktopModeCompatPolicy + DesktopModeCompatPolicy desktopModeCompatPolicy, + DesktopTilingDecorViewModel desktopTilingDecorViewModel ) { if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) { return Optional.empty(); @@ -1006,7 +1005,8 @@ public abstract class WMShellModule { desktopTasksLimiter, appHandleEducationController, appToWebEducationController, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger, - taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy)); + taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy, + desktopTilingDecorViewModel)); } @WMSingleton @@ -1278,10 +1278,10 @@ public abstract class WMShellModule { @WMSingleton @Provides static DesktopWindowingEducationTooltipController - provideDesktopWindowingEducationTooltipController( - Context context, - AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory, - DisplayController displayController) { + provideDesktopWindowingEducationTooltipController( + Context context, + AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory, + DisplayController displayController) { return new DesktopWindowingEducationTooltipController( context, additionalSystemViewContainerFactory, displayController); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index c9b3ec0d3a11..1f7edb413908 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -71,9 +71,31 @@ class DesktopMixedTransitionHandler( wct: WindowContainerTransaction?, ) = freeformTaskTransitionHandler.startWindowingModeTransition(targetWindowingMode, wct) - /** Delegates starting minimized mode transition to [FreeformTaskTransitionHandler]. */ - override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder = - freeformTaskTransitionHandler.startMinimizedModeTransition(wct) + /** + * Starts a minimize transition for [taskId], with [isLastTask] which is true if the task going + * to be minimized is the last visible task. + */ + override fun startMinimizedModeTransition( + wct: WindowContainerTransaction?, + taskId: Int, + isLastTask: Boolean, + ): IBinder { + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue) { + return freeformTaskTransitionHandler.startMinimizedModeTransition( + wct, + taskId, + isLastTask, + ) + } + requireNotNull(wct) + return transitions + .startTransition(Transitions.TRANSIT_MINIMIZE, wct, /* handler= */ this) + .also { transition -> + pendingMixedTransitions.add( + PendingMixedTransition.Minimize(transition, taskId, isLastTask) + ) + } + } /** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */ override fun startPipTransition(wct: WindowContainerTransaction?): IBinder = @@ -298,7 +320,15 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false + val shouldAnimate = + if (info.type == Transitions.TRANSIT_MINIMIZE) { + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue + } else { + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue + } + if (!shouldAnimate) { + return false + } val minimizeChange = findTaskChange(info, pending.minimizingTask) if (minimizeChange == null) { 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 f17b680f6fae..522d83ec50eb 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 @@ -136,7 +136,6 @@ import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionFinishCallback -import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener @@ -144,7 +143,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow import com.android.wm.shell.windowdecor.extension.requestingImmersive -import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel +import com.android.wm.shell.windowdecor.tiling.SnapEventHandler import java.io.PrintWriter import java.util.Optional import java.util.concurrent.Executor @@ -184,7 +183,6 @@ class DesktopTasksController( @ShellMainThread private val handler: Handler, private val desktopModeEventLogger: DesktopModeEventLogger, private val desktopModeUiEventLogger: DesktopModeUiEventLogger, - private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val bubbleController: Optional<BubbleController>, private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver, @@ -204,7 +202,9 @@ class DesktopTasksController( private var userId: Int private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler = DesktopModeShellCommandHandler(this) + private val mOnAnimationFinishedCallback = { releaseVisualIndicator() } + private lateinit var snapEventHandler: SnapEventHandler private val dragToDesktopStateListener = object : DragToDesktopStateListener { override fun onCommitToDesktopAnimationStart() { @@ -269,7 +269,7 @@ class DesktopTasksController( RecentsTransitionStateListener.stateToString(state), ) recentsTransitionState = state - desktopTilingDecorViewModel.onOverviewAnimationStateChange( + snapEventHandler.onOverviewAnimationStateChange( RecentsTransitionStateListener.isAnimating(state) ) } @@ -300,6 +300,11 @@ class DesktopTasksController( dragToDesktopTransitionHandler.setSplitScreenController(controller) } + /** Setter to handle snap events */ + fun setSnapEventHandler(handler: SnapEventHandler) { + snapEventHandler = handler + } + /** Returns the transition type for the given remote transition. */ private fun transitionType(remoteTransition: RemoteTransition?): Int { if (remoteTransition == null) { @@ -784,7 +789,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, ): ((IBinder) -> Unit)? { val taskId = taskInfo.taskId - desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId) + snapEventHandler.removeTaskIfTiled(displayId, taskId) performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false) taskRepository.addClosingTask(displayId, taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( @@ -833,7 +838,7 @@ class DesktopTasksController( val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() - desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId) + snapEventHandler.removeTaskIfTiled(displayId, taskId) performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false) // Notify immersive handler as it might need to exit immersive state. val exitResult = @@ -844,7 +849,9 @@ class DesktopTasksController( ) wct.reorder(taskInfo.token, false) - val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) + val isLastTask = taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId) + val transition: IBinder = + freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask) desktopTasksLimiter.ifPresent { it.addPendingMinimizeChange( transition = transition, @@ -859,7 +866,7 @@ class DesktopTasksController( /** Move a task with given `taskId` to fullscreen */ fun moveToFullscreen(taskId: Int, transitionSource: DesktopModeTransitionSource) { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> - desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, taskId) + snapEventHandler.removeTaskIfTiled(task.displayId, taskId) moveToFullscreenWithAnimation(task, task.positionInParent, transitionSource) } } @@ -867,7 +874,7 @@ class DesktopTasksController( /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */ fun enterFullscreen(displayId: Int, transitionSource: DesktopModeTransitionSource) { getFocusedFreeformTask(displayId)?.let { - desktopTilingDecorViewModel.removeTaskIfTiled(displayId, it.taskId) + snapEventHandler.removeTaskIfTiled(displayId, it.taskId) moveToFullscreenWithAnimation(it, it.positionInParent, transitionSource) } } @@ -986,7 +993,7 @@ class DesktopTasksController( logV("moveTaskToFront taskId=%s", taskInfo.taskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. - if (desktopTilingDecorViewModel.moveTaskToFrontIfTiled(taskInfo)) { + if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) { return } val wct = WindowContainerTransaction() @@ -1228,7 +1235,7 @@ class DesktopTasksController( } else { // Save current bounds so that task can be restored back to original bounds if necessary // and toggle to the stable bounds. - desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) + snapEventHandler.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds) destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo)) } @@ -1354,7 +1361,6 @@ class DesktopTasksController( position: SnapPosition, resizeTrigger: ResizeTrigger, inputMethod: InputMethod, - desktopWindowDecoration: DesktopModeWindowDecoration, ) { desktopModeEventLogger.logTaskResizingStarted( resizeTrigger, @@ -1376,13 +1382,7 @@ class DesktopTasksController( ) if (DesktopModeFlags.ENABLE_TILE_RESIZING.isTrue()) { - val isTiled = - desktopTilingDecorViewModel.snapToHalfScreen( - taskInfo, - desktopWindowDecoration, - position, - currentDragBounds, - ) + val isTiled = snapEventHandler.snapToHalfScreen(taskInfo, currentDragBounds, position) if (isTiled) { taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true) } @@ -1419,7 +1419,6 @@ class DesktopTasksController( position: SnapPosition, resizeTrigger: ResizeTrigger, inputMethod: InputMethod, - desktopModeWindowDecoration: DesktopModeWindowDecoration, ) { if (!isSnapResizingAllowed(taskInfo)) { Toast.makeText( @@ -1438,7 +1437,6 @@ class DesktopTasksController( position, resizeTrigger, inputMethod, - desktopModeWindowDecoration, ) } @@ -1450,7 +1448,6 @@ class DesktopTasksController( currentDragBounds: Rect, dragStartBounds: Rect, motionEvent: MotionEvent, - desktopModeWindowDecoration: DesktopModeWindowDecoration, ) { releaseVisualIndicator() if (!isSnapResizingAllowed(taskInfo)) { @@ -1498,7 +1495,6 @@ class DesktopTasksController( position, resizeTrigger, DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), - desktopModeWindowDecoration, ) } } @@ -2169,7 +2165,7 @@ class DesktopTasksController( return wct } if (!wct.isEmpty) { - desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) + snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId) return wct } return null @@ -2265,7 +2261,7 @@ class DesktopTasksController( if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { taskRepository.addClosingTask(task.displayId, task.taskId) - desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) + snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId) } taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( @@ -2730,7 +2726,7 @@ class DesktopTasksController( taskBounds: Rect, ) { if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return - desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) + snapEventHandler.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) updateVisualIndicator( taskInfo, taskSurface, @@ -2790,7 +2786,6 @@ class DesktopTasksController( validDragArea: Rect, dragStartBounds: Rect, motionEvent: MotionEvent, - desktopModeWindowDecoration: DesktopModeWindowDecoration, ) { if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { return @@ -2829,7 +2824,6 @@ class DesktopTasksController( currentDragBounds, dragStartBounds, motionEvent, - desktopModeWindowDecoration, ) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { @@ -2844,7 +2838,6 @@ class DesktopTasksController( currentDragBounds, dragStartBounds, motionEvent, - desktopModeWindowDecoration, ) } IndicatorType.NO_INDICATOR, @@ -3130,7 +3123,7 @@ class DesktopTasksController( logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) userId = newUserId taskRepository = userRepositories.getProfile(userId) - desktopTilingDecorViewModel.onUserChange() + snapEventHandler.onUserChange() } /** Called when a task's info changes. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index fc29498291da..aaecf8c2d727 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -257,8 +257,8 @@ sealed class DragToDesktopTransitionHandler( // Animation is handled by BubbleController val wct = WindowContainerTransaction() restoreWindowOrder(wct, state) - // TODO(b/388851898): pass along information about left or right side - requestBubbleFromScaledTask(wct) + val onLeft = cancelState == CancelState.CANCEL_BUBBLE_LEFT + requestBubbleFromScaledTask(wct, onLeft) } } else { // There's no dragged task, this can happen when the "cancel" happened too quickly @@ -318,23 +318,27 @@ sealed class DragToDesktopTransitionHandler( splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds) } - private fun requestBubbleFromScaledTask(wct: WindowContainerTransaction) { + private fun requestBubbleFromScaledTask(wct: WindowContainerTransaction, onLeft: Boolean) { // TODO(b/391928049): update density once we can drag from desktop to bubble val state = requireTransitionState() val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo") val taskBounds = getAnimatedTaskBounds() state.dragAnimator.cancelAnimator() - requestBubble(wct, taskInfo, taskBounds) + requestBubble(wct, taskInfo, onLeft, taskBounds) } private fun requestBubble( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo, + onLeft: Boolean, taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds), ) { val controller = bubbleController.orElseThrow { IllegalStateException("BubbleController not set") } - controller.expandStackAndSelectBubble(taskInfo, BubbleTransitions.DragData(taskBounds, wct)) + controller.expandStackAndSelectBubble( + taskInfo, + BubbleTransitions.DragData(taskBounds, wct, onLeft), + ) } override fun startAnimation( @@ -497,8 +501,8 @@ sealed class DragToDesktopTransitionHandler( state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.") val wct = WindowContainerTransaction() restoreWindowOrder(wct) - // TODO(b/388851898): pass along information about left or right side - requestBubble(wct, taskInfo) + val onLeft = state.cancelState == CancelState.CANCEL_BUBBLE_LEFT + requestBubble(wct, taskInfo, onLeft) } return true } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 31715f0444a9..b60fb5e7bfdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -93,7 +93,8 @@ public class FreeformTaskTransitionHandler } @Override - public IBinder startMinimizedModeTransition(WindowContainerTransaction wct) { + public IBinder startMinimizedModeTransition( + WindowContainerTransaction wct, int taskId, boolean isLastTask) { final int type = Transitions.TRANSIT_MINIMIZE; final IBinder token = mTransitions.startTransition(type, wct, this); mPendingTransitionTokens.add(token); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index a874a5be426d..822934c1e646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -38,10 +38,13 @@ public interface FreeformTaskTransitionStarter { * Starts window minimization transition * * @param wct the {@link WindowContainerTransaction} that changes the windowing mode + * @param taskId the task id of the task being minimized + * @param isLastTask true if the task being minimized is the last visible task * * @return the started transition */ - IBinder startMinimizedModeTransition(WindowContainerTransaction wct); + IBinder startMinimizedModeTransition( + WindowContainerTransaction wct, int taskId, boolean isLastTask); /** * Starts close window transition @@ -60,4 +63,4 @@ public interface FreeformTaskTransitionStarter { * @return the started transition */ IBinder startPipTransition(WindowContainerTransaction wct); -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index c0a0f469add4..d666126b91ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; -import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; @@ -201,8 +200,7 @@ public class KeyguardTransitionHandler transition, info, startTransaction, finishTransaction, finishCallback); } - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 - || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) { + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) { return startAnimation(mAppearTransition, "appearing", transition, info, startTransaction, finishTransaction, finishCallback); } 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 a57b4b948b42..9adaa3614a0f 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 @@ -300,6 +300,9 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.setState(PipTransitionState.EXITING_PIP); return startRemoveAnimation(info, startTransaction, finishTransaction, finishCallback); } + // For any unhandled transition, make sure the PiP surface is properly updated, + // i.e. corner and shadow radius. + syncPipSurfaceState(info, startTransaction, finishTransaction); return false; } 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 7aa00370ff58..dd5439a8aa10 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 @@ -389,7 +389,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(mDisplayId); } else if (id == R.id.minimize_window) { - mTaskOperations.minimizeTask(mTaskToken); + // This minimize button uses the same effect for any minimization. The last argument + // doesn't matter. + mTaskOperations.minimizeTask(mTaskToken, mTaskId, /* isLastTask= */ false); } else if (id == R.id.maximize_window) { RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); final DisplayAreaInfo rootDisplayAreaInfo = 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 1cc04b421132..add2c54f0e29 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 @@ -149,6 +149,8 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel; +import com.android.wm.shell.windowdecor.tiling.SnapEventHandler; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import kotlin.Pair; @@ -173,7 +175,7 @@ import java.util.function.Supplier; */ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, - FocusTransitionListener { + FocusTransitionListener, SnapEventHandler { private static final String TAG = "DesktopModeWindowDecorViewModel"; private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; @@ -255,6 +257,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final WindowDecorTaskResourceLoader mTaskResourceLoader; private final RecentsTransitionHandler mRecentsTransitionHandler; private final DesktopModeCompatPolicy mDesktopModeCompatPolicy; + private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel; public DesktopModeWindowDecorViewModel( Context context, @@ -292,7 +295,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, - DesktopModeCompatPolicy desktopModeCompatPolicy) { + DesktopModeCompatPolicy desktopModeCompatPolicy, + DesktopTilingDecorViewModel desktopTilingDecorViewModel) { this( context, shellExecutor, @@ -335,7 +339,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler, - desktopModeCompatPolicy); + desktopModeCompatPolicy, + desktopTilingDecorViewModel); } @VisibleForTesting @@ -381,7 +386,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, - DesktopModeCompatPolicy desktopModeCompatPolicy) { + DesktopModeCompatPolicy desktopModeCompatPolicy, + DesktopTilingDecorViewModel desktopTilingDecorViewModel) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -452,7 +458,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mTaskResourceLoader = taskResourceLoader; mRecentsTransitionHandler = recentsTransitionHandler; mDesktopModeCompatPolicy = desktopModeCompatPolicy; - + mDesktopTilingDecorViewModel = desktopTilingDecorViewModel; + mDesktopTasksController.setSnapEventHandler(this); shellInit.addInitCallback(this::onInit, this); } @@ -723,8 +730,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, decoration.mTaskInfo, left ? SnapPosition.LEFT : SnapPosition.RIGHT, left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU, - inputMethod, - decoration); + inputMethod); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); @@ -885,6 +891,33 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return snapshotList; } + @Override + public boolean snapToHalfScreen(@NonNull RunningTaskInfo taskInfo, + @NonNull Rect currentDragBounds, @NonNull SnapPosition position) { + return mDesktopTilingDecorViewModel.snapToHalfScreen(taskInfo, + mWindowDecorByTaskId.get(taskInfo.taskId), position, currentDragBounds); + } + + @Override + public void removeTaskIfTiled(int displayId, int taskId) { + mDesktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId); + } + + @Override + public void onUserChange() { + mDesktopTilingDecorViewModel.onUserChange(); + } + + @Override + public void onOverviewAnimationStateChange(boolean running) { + mDesktopTilingDecorViewModel.onOverviewAnimationStateChange(running); + } + + @Override + public boolean moveTaskToFrontIfTiled(@NonNull RunningTaskInfo taskInfo) { + return mDesktopTilingDecorViewModel.moveTaskToFrontIfTiled(taskInfo); + } + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { @@ -1238,8 +1271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, taskInfo, decoration.mTaskSurface, new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), newTaskBounds, decoration.calculateValidDragArea(), - new Rect(mOnDragStartInitialBounds), e, - mWindowDecorByTaskId.get(taskInfo.taskId)); + new Rect(mOnDragStartInitialBounds), e); if (touchingButton) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java index bc85d2b40748..45ba4413814c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java @@ -86,14 +86,18 @@ class TaskOperations { return null; } - IBinder minimizeTask(WindowContainerToken taskToken) { - return minimizeTask(taskToken, new WindowContainerTransaction()); + IBinder minimizeTask(WindowContainerToken taskToken, int taskId, boolean isLastTask) { + return minimizeTask(taskToken, taskId, isLastTask, new WindowContainerTransaction()); } - IBinder minimizeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) { + IBinder minimizeTask( + WindowContainerToken taskToken, + int taskId, + boolean isLastTask, + WindowContainerTransaction wct) { wct.reorder(taskToken, false); if (Transitions.ENABLE_SHELL_TRANSITIONS) { - return mTransitionStarter.startMinimizedModeTransition(wct); + return mTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask); } else { mSyncQueue.queue(wct); return null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt new file mode 100644 index 000000000000..52e24d6fe0d0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 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.windowdecor.tiling + +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.Rect +import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition + +/** Interface for handling snap to half screen events. */ +interface SnapEventHandler { + /** Snaps an app to half the screen for tiling. */ + fun snapToHalfScreen( + taskInfo: RunningTaskInfo, + currentDragBounds: Rect, + position: SnapPosition, + ): Boolean + + /** Removes a task from tiling if it's tiled, for example on task exiting. */ + fun removeTaskIfTiled(displayId: Int, taskId: Int) + + /** Notifies the tiling handler of user switch. */ + fun onUserChange() + + /** Notifies the tiling handler of overview animation state change. */ + fun onOverviewAnimationStateChange(running: Boolean) + + /** If a task is tiled, delegate moving to front to tiling infrastructure. */ + fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java index 87ee4f58bfdd..42310caba1c6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -215,7 +215,7 @@ public class BubbleTransitionsTest extends ShellTestCase { pendingWct.reorder(pendingDragOpToken, /* onTop= */ false); BubbleTransitions.DragData dragData = new BubbleTransitions.DragData( - draggedTaskBounds, pendingWct + draggedTaskBounds, pendingWct, /* releasedOnLeft= */ false ); final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 0b41952a89d7..77cd1e56853d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -58,6 +58,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito @@ -128,12 +129,21 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test fun startMinimizedModeTransition_callsFreeformTaskTransitionHandler() { val wct = WindowContainerTransaction() - whenever(freeformTaskTransitionHandler.startMinimizedModeTransition(any())) + val taskId = 1 + val isLastTask = false + whenever( + freeformTaskTransitionHandler.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(mock()) - mixedHandler.startMinimizedModeTransition(wct) + mixedHandler.startMinimizedModeTransition(wct, taskId, isLastTask) - verify(freeformTaskTransitionHandler).startMinimizedModeTransition(wct) + verify(freeformTaskTransitionHandler) + .startMinimizedModeTransition(eq(wct), eq(taskId), eq(isLastTask)) } @Test @@ -531,6 +541,131 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX) + fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsDisabled_doesNotUseMixedHandler() { + val wct = WindowContainerTransaction() + val task = createTask(WINDOWING_MODE_FREEFORM) + whenever( + freeformTaskTransitionHandler.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) + .thenReturn(mock()) + + mixedHandler.startMinimizedModeTransition( + wct = wct, + taskId = task.taskId, + isLastTask = true, + ) + + verify(freeformTaskTransitionHandler) + .startMinimizedModeTransition(eq(wct), eq(task.taskId), eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX) + fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsEnabled_notLastTask_callsMinimizationHandler() { + val wct = WindowContainerTransaction() + val minimizingTask = createTask(WINDOWING_MODE_FREEFORM) + val minimizingTaskChange = createChange(minimizingTask) + val transition = Binder() + whenever( + transitions.startTransition(eq(Transitions.TRANSIT_MINIMIZE), eq(wct), anyOrNull()) + ) + .thenReturn(transition) + whenever( + desktopMinimizationTransitionHandler.startAnimation( + any(), + any(), + any(), + any(), + any(), + ) + ) + .thenReturn(true) + + mixedHandler.startMinimizedModeTransition( + wct = wct, + taskId = minimizingTask.taskId, + isLastTask = false, + ) + val started = + mixedHandler.startAnimation( + transition = transition, + info = + createCloseTransitionInfo( + Transitions.TRANSIT_MINIMIZE, + listOf(minimizingTaskChange), + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) + + assertTrue("Should delegate animation to minimization transition handler", started) + verify(desktopMinimizationTransitionHandler) + .startAnimation( + eq(transition), + argThat { info -> info.changes.contains(minimizingTaskChange) }, + any(), + any(), + any(), + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX) + fun startMinimizedModeTransition_exitByMinimizeTransitionFlagsEnabled_withMinimizingLastTask_dispatchesTransition() { + val wct = WindowContainerTransaction() + val minimizingTask = createTask(WINDOWING_MODE_FREEFORM) + val minimizingTaskChange = createChange(minimizingTask) + val transition = Binder() + whenever( + transitions.startTransition(eq(Transitions.TRANSIT_MINIMIZE), eq(wct), anyOrNull()) + ) + .thenReturn(transition) + whenever( + desktopMinimizationTransitionHandler.startAnimation( + any(), + any(), + any(), + any(), + any(), + ) + ) + .thenReturn(true) + + mixedHandler.startMinimizedModeTransition( + wct = wct, + taskId = minimizingTask.taskId, + isLastTask = true, + ) + mixedHandler.startAnimation( + transition = transition, + info = + createCloseTransitionInfo( + Transitions.TRANSIT_MINIMIZE, + listOf(minimizingTaskChange), + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) + + verify(transitions) + .dispatchTransition( + eq(transition), + argThat { info -> info.changes.contains(minimizingTaskChange) }, + any(), + any(), + any(), + eq(mixedHandler), + ) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() 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 3ee9501dd8dd..08a4bb2db1ee 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 @@ -151,8 +151,7 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME -import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration -import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel +import com.android.wm.shell.windowdecor.tiling.SnapEventHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import java.util.Optional @@ -179,6 +178,7 @@ import org.mockito.ArgumentMatchers.isA import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock @@ -233,6 +233,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator @Mock lateinit var recentTasksController: RecentTasksController + @Mock lateinit var snapEventHandler: SnapEventHandler @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockSurface: SurfaceControl @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener @@ -245,9 +246,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer @Mock private lateinit var mockToast: Toast private lateinit var mockitoSession: StaticMockitoSession - @Mock private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel @Mock private lateinit var bubbleController: BubbleController - @Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration @Mock private lateinit var resources: Resources @Mock lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener @@ -379,6 +378,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() recentsTransitionStateListener = captor.firstValue controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener + controller.setSnapEventHandler(snapEventHandler) assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -422,7 +422,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() mockHandler, desktopModeEventLogger, desktopModeUiEventLogger, - desktopTilingDecorViewModel, desktopWallpaperActivityTokenProvider, Optional.of(bubbleController), overviewToDesktopTransitionObserver, @@ -2763,13 +2762,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = false) val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false)) captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() } @@ -2785,18 +2791,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(freeformTaskTransitionStarter).startPipTransition(any()) - verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) + verify(freeformTaskTransitionStarter, never()) + .startMinimizedModeTransition(any(), anyInt(), anyBoolean()) } @Test fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() { val task = setUpPipTask(autoEnterEnabled = false) - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(Binder()) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) } @@ -2820,13 +2834,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = true) val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true)) captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK } } @@ -2835,14 +2856,21 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() { val task = setUpFreeformTask() val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) // The only active task is being minimized. controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true)) // Adds remove wallpaper operation captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false) } @@ -2851,7 +2879,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() { val task = setUpFreeformTask() val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) @@ -2859,7 +2893,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false)) captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() } @@ -2870,13 +2905,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task1 = setUpFreeformTask(active = true) setUpFreeformTask(active = true) val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(false)) captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() } @@ -2888,7 +2930,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task1 = setUpFreeformTask(active = true) val task2 = setUpFreeformTask(active = true) val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) @@ -2896,7 +2944,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) // Adds remove wallpaper operation val captor = argumentCaptor<WindowContainerTransaction>() - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(true)) // Adds remove wallpaper operation captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false) } @@ -2905,7 +2954,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() fun onDesktopWindowMinimize_triesToExitImmersive() { val task = setUpFreeformTask() val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) @@ -2918,7 +2973,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFreeformTask() val transition = Binder() val runOnTransit = RunOnStartTransitionCallback() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) .thenReturn(transition) whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) .thenReturn( @@ -4420,7 +4481,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) val rectAfterEnd = Rect(100, 50, 500, 1150) verify(transitions) @@ -4458,7 +4518,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) verify(transitions) @@ -4498,7 +4557,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) verify(transitions) @@ -4539,7 +4597,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) // Assert the task exits desktop mode @@ -4577,7 +4634,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) // Assert bounds set to stable bounds @@ -4633,7 +4689,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = Rect(), motionEvent, - desktopWindowDecoration, ) // Assert that task is NOT updated via WCT @@ -5053,7 +5108,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, - desktopWindowDecoration, ) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) @@ -5099,7 +5153,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, - desktopWindowDecoration, ) // Assert that task is NOT updated via WCT verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) @@ -5143,7 +5196,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() currentDragBounds, preDragBounds, motionEvent, - desktopWindowDecoration, ) val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) @@ -5173,7 +5225,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() currentDragBounds, preDragBounds, motionEvent, - desktopWindowDecoration, ) verify(mReturnToDragStartAnimator) .start( @@ -5198,7 +5249,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.MOUSE, - desktopWindowDecoration, ) // Assert that task is NOT updated via WCT @@ -5225,7 +5275,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.MOUSE, - desktopWindowDecoration, ) // Assert bounds set to half of the stable bounds diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index ba26d1df94f6..85f6cd36992d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -51,6 +51,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.MockitoSession +import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -180,10 +181,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { defaultHandler, DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_LEFT, ) - verify(bubbleController).expandStackAndSelectBubble( - any<RunningTaskInfo>(), - any<BubbleTransitions.DragData>() - ) + verify(bubbleController) + .expandStackAndSelectBubble( + any<RunningTaskInfo>(), + argThat<BubbleTransitions.DragData> { isReleasedOnLeft }, + ) } @Test @@ -192,10 +194,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { defaultHandler, DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_RIGHT, ) - verify(bubbleController).expandStackAndSelectBubble( - any<RunningTaskInfo>(), - any<BubbleTransitions.DragData>() - ) + verify(bubbleController) + .expandStackAndSelectBubble( + any<RunningTaskInfo>(), + argThat<BubbleTransitions.DragData> { !isReleasedOnLeft }, + ) } @Test @@ -382,10 +385,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) // Verify the request went through bubble controller. - verify(bubbleController).expandStackAndSelectBubble( - any<RunningTaskInfo>(), - any<BubbleTransitions.DragData>() - ) + verify(bubbleController) + .expandStackAndSelectBubble( + any<RunningTaskInfo>(), + argThat<BubbleTransitions.DragData> { isReleasedOnLeft }, + ) } @Test @@ -398,10 +402,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) // Verify the request went through bubble controller. - verify(bubbleController).expandStackAndSelectBubble( - any<RunningTaskInfo>(), - any<BubbleTransitions.DragData>() - ) + verify(bubbleController) + .expandStackAndSelectBubble( + any<RunningTaskInfo>(), + argThat<BubbleTransitions.DragData> { !isReleasedOnLeft }, + ) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt index 391d46287498..741a0fdcf63c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt @@ -157,23 +157,40 @@ class DesktopModeStatusTest : ShellTestCase() { } @Test - fun isInternalDisplayEligibleToHostDesktops_configDEModeOn_returnsTrue() { + fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktop_returnsTrue() { + doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)) doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) - assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue() + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue() + } + + @Test + fun isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() { + doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)) + doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) + + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() + } + + @Test + fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() { + doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)) + doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) + + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() } @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @Test fun isInternalDisplayEligibleToHostDesktops_supportFlagOff_returnsFalse() { - assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse() + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() } @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @Test fun isInternalDisplayEligibleToHostDesktops_supportFlagOn_returnsFalse() { - assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse() + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() } @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @@ -183,7 +200,7 @@ class DesktopModeStatusTest : ShellTestCase() { eq(R.bool.config_isDesktopModeDevOptionSupported) ) - assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue() + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue() } @DisableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION) 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 da41a23f066c..d8d45c02b364 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 @@ -484,7 +484,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(SnapPosition.LEFT), eq(ResizeTrigger.SNAP_LEFT_MENU), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor) ) } @@ -520,7 +519,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(SnapPosition.LEFT), eq(ResizeTrigger.SNAP_LEFT_MENU), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor), ) } @@ -542,7 +540,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT), eq(ResizeTrigger.MAXIMIZE_BUTTON), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor), ) } @@ -562,7 +559,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(SnapPosition.RIGHT), eq(ResizeTrigger.SNAP_RIGHT_MENU), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor), ) } @@ -598,7 +594,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(SnapPosition.RIGHT), eq(ResizeTrigger.SNAP_RIGHT_MENU), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor), ) } @@ -620,7 +615,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT), eq(ResizeTrigger.MAXIMIZE_BUTTON), eq(InputMethod.UNKNOWN_INPUT_METHOD), - eq(decor), ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index e40034b09f39..8cccdb2b6120 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -81,6 +81,7 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopM import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier +import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder import org.junit.After import org.mockito.Mockito @@ -147,6 +148,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>() protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>() protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>() + protected val mockTilingWindowDecoration = mock<DesktopTilingDecorViewModel>() protected val motionEvent = mock<MotionEvent>() private val displayLayout = mock<DisplayLayout>() private val display = mock<Display>() @@ -226,6 +228,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mock<WindowDecorTaskResourceLoader>(), mockRecentsTransitionHandler, desktopModeCompatPolicy, + mockTilingWindowDecoration, ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index dbb891455ddd..e693fcfd3918 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -162,10 +162,13 @@ const std::string& ApkAssets::GetDebugName() const { return assets_provider_->GetDebugName(); } -bool ApkAssets::IsUpToDate() const { +UpToDate ApkAssets::IsUpToDate() const { // Loaders are invalidated by the app, not the system, so assume they are up to date. - return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate()) - && assets_provider_->IsUpToDate()); + if (IsLoader()) { + return UpToDate::Always; + } + const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always; + return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); }); } } // namespace android diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index 2d3c06506a1f..808509120462 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -24,9 +24,27 @@ #include <ziparchive/zip_archive.h> namespace android { -namespace { -constexpr const char* kEmptyDebugString = "<empty>"; -} // namespace + +static constexpr std::string_view kEmptyDebugString = "<empty>"; + +std::unique_ptr<AssetsProvider> AssetsProvider::CreateWithOverride( + std::unique_ptr<AssetsProvider> provider, std::unique_ptr<AssetsProvider> override) { + if (provider == nullptr) { + return {}; + } + if (override == nullptr) { + return provider; + } + return MultiAssetsProvider::Create(std::move(override), std::move(provider)); +} + +std::unique_ptr<AssetsProvider> AssetsProvider::CreateFromNullable( + std::unique_ptr<AssetsProvider> nullable) { + if (nullable) { + return nullable; + } + return EmptyAssetsProvider::Create(); +} std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode, bool* file_exists) const { @@ -86,11 +104,9 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const { } ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path, - package_property_t flags, time_t last_mod_time) - : zip_handle_(handle), - name_(std::move(path)), - flags_(flags), - last_mod_time_(last_mod_time) {} + package_property_t flags, ModDate last_mod_time) + : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) { +} std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, package_property_t flags, @@ -104,10 +120,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. - if (!isReadonlyFilesystem(path.c_str())) { - if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) { + if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) { + if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -116,7 +132,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, } return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime)); + new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date)); } std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, @@ -137,10 +153,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. if (!isReadonlyFilesystem(released_fd)) { - if (fstat(released_fd, &sb) < 0) { + if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -150,7 +166,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider( - handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime)); + handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date)); } std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, @@ -282,21 +298,16 @@ const std::string& ZipAssetsProvider::GetDebugName() const { return name_.GetDebugName(); } -bool ZipAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; +UpToDate ZipAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - struct stat sb{}; - if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) { - // If fstat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; - } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get()))); } -DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time) - : dir_(std::move(path)), last_mod_time_(last_mod_time) {} +DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time) + : dir_(std::move(path)), last_mod_time_(last_mod_time) { +} std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) { struct stat sb; @@ -317,7 +328,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st const bool isReadonly = isReadonlyFilesystem(path.c_str()); return std::unique_ptr<DirectoryAssetsProvider>( - new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime)); + new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb))); } std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path, @@ -346,17 +357,11 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { return dir_; } -bool DirectoryAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; +UpToDate DirectoryAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - struct stat sb; - if (stat(dir_.c_str(), &sb) < 0) { - // If stat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; - } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(dir_.c_str())); } MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary, @@ -397,8 +402,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const { return debug_name_; } -bool MultiAssetsProvider::IsUpToDate() const { - return primary_->IsUpToDate() && secondary_->IsUpToDate(); +UpToDate MultiAssetsProvider::IsUpToDate() const { + return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); }); } EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) : @@ -438,12 +443,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const { if (path_.has_value()) { return *path_; } - const static std::string kEmpty = kEmptyDebugString; + constexpr static std::string kEmpty{kEmptyDebugString}; return kEmpty; } -bool EmptyAssetsProvider::IsUpToDate() const { - return true; +UpToDate EmptyAssetsProvider::IsUpToDate() const { + return UpToDate::Always; } } // namespace android diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 095be57a5dc8..f0ef97e5bdcc 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -22,9 +22,10 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "android-base/utf8.h" -#include "androidfw/misc.h" +#include "androidfw/AssetManager.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" +#include "androidfw/misc.h" #include "utils/ByteOrder.h" #include "utils/Trace.h" @@ -280,11 +281,16 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head configurations_(configs), overlay_entries_(overlay_entries), string_pool_(std::move(string_pool)), - idmap_fd_( - android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)), overlay_apk_path_(overlay_apk_path), target_apk_path_(target_apk_path), - idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) { + idmap_last_mod_time_(kInvalidModDate) { + if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) || + !(target_apk_path_ == AssetManager::TARGET_APK_PATH || + isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) { + idmap_fd_.reset( + android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)); + idmap_last_mod_time_ = getFileModDate(idmap_fd_); + } } std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { @@ -405,8 +411,11 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie std::move(idmap_string_pool),*overlay_path, *target_path)); } -bool LoadedIdmap::IsUpToDate() const { - return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()); +UpToDate LoadedIdmap::IsUpToDate() const { + if (idmap_last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; + } + return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get())); } } // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 978bc768cd3d..a18c5f5f92f6 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -152,12 +152,11 @@ static void fill9patchOffsets(Res_png_9patch* patch) { patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t)); } -void Res_value::copyFrom_dtoh(const Res_value& src) -{ - size = dtohs(src.size); - res0 = src.res0; - dataType = src.dataType; - data = dtohl(src.data); +void Res_value::copyFrom_dtoh_slow(const Res_value& src) { + size = dtohs(src.size); + res0 = src.res0; + dataType = src.dataType; + data = dtohl(src.data); } void Res_png_9patch::deviceToFile() @@ -2035,16 +2034,6 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const // -------------------------------------------------------------------- // -------------------------------------------------------------------- -void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) { - const size_t size = dtohl(o.size); - if (size >= sizeof(ResTable_config)) { - *this = o; - } else { - memcpy(this, &o, size); - memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); - } -} - /* static */ size_t unpackLanguageOrRegion(const char in[2], const char base, char out[4]) { if (in[0] & 0x80) { @@ -2109,34 +2098,33 @@ size_t ResTable_config::unpackRegion(char region[4]) const { return unpackLanguageOrRegion(this->country, '0', region); } - -void ResTable_config::copyFromDtoH(const ResTable_config& o) { - copyFromDeviceNoSwap(o); - size = sizeof(ResTable_config); - mcc = dtohs(mcc); - mnc = dtohs(mnc); - density = dtohs(density); - screenWidth = dtohs(screenWidth); - screenHeight = dtohs(screenHeight); - sdkVersion = dtohs(sdkVersion); - minorVersion = dtohs(minorVersion); - smallestScreenWidthDp = dtohs(smallestScreenWidthDp); - screenWidthDp = dtohs(screenWidthDp); - screenHeightDp = dtohs(screenHeightDp); -} - -void ResTable_config::swapHtoD() { - size = htodl(size); - mcc = htods(mcc); - mnc = htods(mnc); - density = htods(density); - screenWidth = htods(screenWidth); - screenHeight = htods(screenHeight); - sdkVersion = htods(sdkVersion); - minorVersion = htods(minorVersion); - smallestScreenWidthDp = htods(smallestScreenWidthDp); - screenWidthDp = htods(screenWidthDp); - screenHeightDp = htods(screenHeightDp); +void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) { + copyFromDeviceNoSwap(o); + size = sizeof(ResTable_config); + mcc = dtohs(mcc); + mnc = dtohs(mnc); + density = dtohs(density); + screenWidth = dtohs(screenWidth); + screenHeight = dtohs(screenHeight); + sdkVersion = dtohs(sdkVersion); + minorVersion = dtohs(minorVersion); + smallestScreenWidthDp = dtohs(smallestScreenWidthDp); + screenWidthDp = dtohs(screenWidthDp); + screenHeightDp = dtohs(screenHeightDp); +} + +void ResTable_config::swapHtoD_slow() { + size = htodl(size); + mcc = htods(mcc); + mnc = htods(mnc); + density = htods(density); + screenWidth = htods(screenWidth); + screenHeight = htods(screenHeight); + sdkVersion = htods(sdkVersion); + minorVersion = htods(minorVersion); + smallestScreenWidthDp = htods(smallestScreenWidthDp); + screenWidthDp = htods(screenWidthDp); + screenHeightDp = htods(screenHeightDp); } /* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) { @@ -2149,7 +2137,7 @@ void ResTable_config::swapHtoD() { // systems should happen very infrequently (if at all.) // The comparison code relies on memcmp low-level optimizations that make it // more efficient than strncmp. - const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; + static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index be55fe8b4bb6..86c459fb4647 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -32,13 +32,18 @@ namespace android { namespace util { void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) { - char buf[5]; - while (*src && len != 0) { - char16_t c = static_cast<char16_t>(dtohs(*src)); - utf16_to_utf8(&c, 1, buf, sizeof(buf)); - out->append(buf, strlen(buf)); - ++src; - --len; + static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + if constexpr (kDeviceEndiannessSame) { + *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)}); + } else { + char buf[5]; + while (*src && len != 0) { + char16_t c = static_cast<char16_t>(dtohs(*src)); + utf16_to_utf8(&c, 1, buf, sizeof(buf)); + out->append(buf, strlen(buf)); + ++src; + --len; + } } } @@ -63,8 +68,10 @@ std::string Utf16ToUtf8(StringPiece16 utf16) { } std::string utf8; - utf8.resize(utf8_length); - utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1); + utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) { + utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1); + return size; + }); return utf8; } diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 231808beb718..3f6f4661f2f7 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -116,7 +116,7 @@ class ApkAssets : public RefBase { return resources_asset_ != nullptr && resources_asset_->isAllocated(); } - bool IsUpToDate() const; + UpToDate IsUpToDate() const; // DANGER! // This is a destructive method that rips the assets provider out of ApkAssets object. diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index d33c325ff369..037f684f5b78 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROIDFW_ASSETSPROVIDER_H -#define ANDROIDFW_ASSETSPROVIDER_H +#pragma once #include <memory> #include <string> @@ -37,6 +36,12 @@ namespace android { struct AssetsProvider { static constexpr off64_t kUnknownLength = -1; + static std::unique_ptr<AssetsProvider> CreateWithOverride( + std::unique_ptr<AssetsProvider> provider, std::unique_ptr<AssetsProvider> override); + + static std::unique_ptr<AssetsProvider> CreateFromNullable( + std::unique_ptr<AssetsProvider> nullable); + // Opens a file for reading. If `file_exists` is not null, it will be set to `true` if the file // exists. This is useful for determining if the file exists but was unable to be opened due to // an I/O error. @@ -58,7 +63,7 @@ struct AssetsProvider { WARN_UNUSED virtual const std::string& GetDebugName() const = 0; // Returns whether the interface provides the most recent version of its files. - WARN_UNUSED virtual bool IsUpToDate() const = 0; + WARN_UNUSED virtual UpToDate IsUpToDate() const = 0; // Creates an Asset from a file on disk. static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); @@ -95,7 +100,7 @@ struct ZipAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const; ~ZipAssetsProvider() override = default; @@ -106,7 +111,7 @@ struct ZipAssetsProvider : public AssetsProvider { private: struct PathOrDebugName; ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags, - time_t last_mod_time); + ModDate last_mod_time); struct PathOrDebugName { static PathOrDebugName Path(std::string value) { @@ -135,7 +140,7 @@ struct ZipAssetsProvider : public AssetsProvider { std::unique_ptr<ZipArchive, ZipCloser> zip_handle_; PathOrDebugName name_; package_property_t flags_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a root directory. @@ -147,7 +152,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~DirectoryAssetsProvider() override = default; protected: @@ -156,9 +161,9 @@ struct DirectoryAssetsProvider : public AssetsProvider { bool* file_exists) const override; private: - explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time); + explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time); std::string dir_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a `primary` asset provider and falls back to supplying assets from the @@ -172,7 +177,7 @@ struct MultiAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~MultiAssetsProvider() override = default; protected: @@ -199,7 +204,7 @@ struct EmptyAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~EmptyAssetsProvider() override = default; protected: @@ -212,5 +217,3 @@ struct EmptyAssetsProvider : public AssetsProvider { }; } // namespace android - -#endif /* ANDROIDFW_ASSETSPROVIDER_H */ diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index d1db13f53069..0c0856315d8f 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef IDMAP_H_ -#define IDMAP_H_ +#pragma once #include <memory> #include <string> @@ -32,6 +31,31 @@ namespace android { +// An enum that tracks more states than just 'up to date' or 'not' for a resources container: +// there are several cases where we know for sure that the object can't change and won't get +// out of date. Reporting those states to the managed layer allows it to stop checking here +// completely, speeding up the cache lookups by dozens of milliseconds. +enum class UpToDate : int { False, True, Always }; + +// Combines two UpToDate values, and only accesses the second one if it matters to the result. +template <class Getter> +UpToDate combine(UpToDate first, Getter secondGetter) { + switch (first) { + case UpToDate::False: + return UpToDate::False; + case UpToDate::True: { + const auto second = secondGetter(); + return second == UpToDate::False ? UpToDate::False : UpToDate::True; + } + case UpToDate::Always: + return secondGetter(); + } +} + +inline UpToDate fromBool(bool value) { + return value ? UpToDate::True : UpToDate::False; +} + class LoadedIdmap; class IdmapResMap; struct Idmap_header; @@ -197,7 +221,7 @@ class LoadedIdmap { // Returns whether the idmap file on disk has not been modified since the construction of this // LoadedIdmap. - bool IsUpToDate() const; + UpToDate IsUpToDate() const; protected: // Exposed as protected so that tests can subclass and mock this class out. @@ -237,5 +261,3 @@ class LoadedIdmap { }; } // namespace android - -#endif // IDMAP_H_ diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 8b2871c21a1e..30594dcfa939 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -47,6 +47,8 @@ namespace android { +constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + constexpr const uint32_t kIdmapMagic = 0x504D4449u; constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Bu; @@ -408,7 +410,16 @@ struct Res_value typedef uint32_t data_type; data_type data; - void copyFrom_dtoh(const Res_value& src); + void copyFrom_dtoh(const Res_value& src) { + if constexpr (kDeviceEndiannessSame) { + *this = src; + } else { + copyFrom_dtoh_slow(src); + } + } + + private: + void copyFrom_dtoh_slow(const Res_value& src); }; /** @@ -1254,11 +1265,32 @@ struct ResTable_config // Varies in length from 3 to 8 chars. Zero-filled value. char localeNumberingSystem[8]; - void copyFromDeviceNoSwap(const ResTable_config& o); - - void copyFromDtoH(const ResTable_config& o); - - void swapHtoD(); + void copyFromDeviceNoSwap(const ResTable_config& o) { + const auto o_size = dtohl(o.size); + if (o_size >= sizeof(ResTable_config)) [[likely]] { + *this = o; + } else { + memcpy(this, &o, o_size); + memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size); + } + this->size = sizeof(*this); + } + + void copyFromDtoH(const ResTable_config& o) { + if constexpr (kDeviceEndiannessSame) { + copyFromDeviceNoSwap(o); + } else { + copyFromDtoH_slow(o); + } + } + + void swapHtoD() { + if constexpr (kDeviceEndiannessSame) { + ; // noop + } else { + swapHtoD_slow(); + } + } int compare(const ResTable_config& o) const; int compareLogical(const ResTable_config& o) const; @@ -1384,6 +1416,10 @@ struct ResTable_config bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const; String8 toString() const; + + private: + void copyFromDtoH_slow(const ResTable_config& o); + void swapHtoD_slow(); }; /** diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index c9ba8a01a5e9..d8ca64a174a2 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -15,6 +15,7 @@ */ #pragma once +#include <sys/stat.h> #include <time.h> // @@ -64,10 +65,15 @@ ModDate getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ ModDate getFileModDate(int fd); +// Extract the modification date from the stat structure. +ModDate getModDate(const struct ::stat& st); + // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); +bool isKnownWritablePath(const char* path); + } // namespace android // Whoever uses getFileModDate() will need this as well diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 32f3624a3aee..26eb320805c9 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -16,10 +16,10 @@ #define LOG_TAG "misc" -// -// Miscellaneous utility functions. -// -#include <androidfw/misc.h> +#include "androidfw/misc.h" + +#include <errno.h> +#include <sys/stat.h> #include "android-base/logging.h" @@ -28,9 +28,7 @@ #include <sys/vfs.h> #endif // __linux__ -#include <errno.h> -#include <sys/stat.h> - +#include <array> #include <cstdio> #include <cstring> #include <tuple> @@ -40,28 +38,26 @@ namespace android { /* * Get a file's type. */ -FileType getFileType(const char* fileName) -{ - struct stat sb; - - if (stat(fileName, &sb) < 0) { - if (errno == ENOENT || errno == ENOTDIR) - return kFileTypeNonexistent; - else { - PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; - return kFileTypeUnknown; - } - } else { - if (S_ISREG(sb.st_mode)) - return kFileTypeRegular; - else if (S_ISDIR(sb.st_mode)) - return kFileTypeDirectory; - else if (S_ISCHR(sb.st_mode)) - return kFileTypeCharDev; - else if (S_ISBLK(sb.st_mode)) - return kFileTypeBlockDev; - else if (S_ISFIFO(sb.st_mode)) - return kFileTypeFifo; +FileType getFileType(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return kFileTypeNonexistent; + else { + PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; + return kFileTypeUnknown; + } + } else { + if (S_ISREG(sb.st_mode)) + return kFileTypeRegular; + else if (S_ISDIR(sb.st_mode)) + return kFileTypeDirectory; + else if (S_ISCHR(sb.st_mode)) + return kFileTypeCharDev; + else if (S_ISBLK(sb.st_mode)) + return kFileTypeBlockDev; + else if (S_ISFIFO(sb.st_mode)) + return kFileTypeFifo; #if defined(S_ISLNK) else if (S_ISLNK(sb.st_mode)) return kFileTypeSymlink; @@ -75,7 +71,7 @@ FileType getFileType(const char* fileName) } } -static ModDate getModDate(const struct stat& st) { +ModDate getModDate(const struct stat& st) { #ifdef _WIN32 return st.st_mtime; #elif defined(__APPLE__) @@ -113,8 +109,14 @@ bool isReadonlyFilesystem(const char*) { bool isReadonlyFilesystem(int) { return false; } +bool isKnownWritablePath(const char*) { + return false; +} #else // __linux__ bool isReadonlyFilesystem(const char* path) { + if (isKnownWritablePath(path)) { + return false; + } struct statfs sfs; if (::statfs(path, &sfs)) { PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed"; @@ -131,6 +133,13 @@ bool isReadonlyFilesystem(int fd) { } return (sfs.f_flags & ST_RDONLY) != 0; } + +bool isKnownWritablePath(const char* path) { + // We know that all paths in /data/ are writable. + static constexpr char kRwPrefix[] = "/data/"; + return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0; +} + #endif // __linux__ } // namespace android diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index cb2e56f5f5e4..22b9e69500d9 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -218,10 +218,11 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { auto apk_assets = ApkAssets::LoadOverlay(temp_file.path); ASSERT_NE(nullptr, apk_assets); - ASSERT_TRUE(apk_assets->IsUpToDate()); + ASSERT_TRUE(apk_assets->IsOverlay()); + ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate()); unlink(temp_file.path); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); const auto sleep_duration = std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); @@ -230,7 +231,27 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { base::WriteStringToFile("hello", temp_file.path); std::this_thread::sleep_for(sleep_duration); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); +} + +TEST(IdmapTestUpToDate, Combine) { + ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] { + ADD_FAILURE(); // Shouldn't get called at all. + return UpToDate::False; + })); + + ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; })); + + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; })); + + ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; })); +} + +TEST(IdmapTestUpToDate, FromBool) { + ASSERT_EQ(UpToDate::False, fromBool(false)); + ASSERT_EQ(UpToDate::True, fromBool(true)); } } // namespace diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 62fd7d358123..7e1f2e2a3490 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -174,7 +174,7 @@ flag { flag { name: "early_preload_gl_context" namespace: "core_graphics" - description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL." + description: "Preload GL context on renderThread preload. This improves app startup time for apps using GL." bug: "383612849" } @@ -187,4 +187,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "early_preinit_buffer_allocator" + namespace: "core_graphics" + description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency." + bug: "389908734" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index df9f83036709..99e7740d66d2 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -52,6 +52,9 @@ #include <renderthread/RenderThread.h> #include <src/image/SkImage_Base.h> #include <thread/CommonPool.h> +#ifdef __ANDROID__ +#include <ui/GraphicBufferAllocator.h> +#endif #include <utils/Color.h> #include <utils/RefBase.h> #include <utils/StrongPointer.h> @@ -849,6 +852,17 @@ static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) { RenderProxy::preload(); } +static void android_view_ThreadedRenderer_preInitBufferAllocator(JNIEnv*, jclass) { +#ifdef __ANDROID__ + CommonPool::async([] { + ATRACE_NAME("preInitBufferAllocator:GraphicBufferAllocator"); + // This involves several binder calls which we do not want blocking + // critical path of the activity that is launching. + GraphicBufferAllocator::getInstance(); + }); +#endif +} + static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jobject clazz, jboolean enabled) { RenderProxy::setRtAnimationsEnabled(enabled); @@ -1040,6 +1054,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_setDisplayDensityDpi}, {"nInitDisplayInfo", "(IIFIJJZZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo}, {"preload", "()V", (void*)android_view_ThreadedRenderer_preload}, + {"preInitBufferAllocator", "()V", + (void*)android_view_ThreadedRenderer_preInitBufferAllocator}, {"isWebViewOverlaysEnabled", "()Z", (void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled}, {"nSetDrawingEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setDrawingEnabled}, |