summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/shared/res/color/bubble_drop_target_background_color.xml20
-rw-r--r--libs/WindowManager/Shell/shared/res/drawable/bubble_drop_target_background.xml25
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml9
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt90
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/SnapEventHandler.kt43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt141
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt3
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetsProvider.cpp91
-rw-r--r--libs/androidfw/Idmap.cpp21
-rw-r--r--libs/androidfw/ResourceTypes.cpp78
-rw-r--r--libs/androidfw/Util.cpp25
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h2
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h29
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h32
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/androidfw/include/androidfw/misc.h6
-rw-r--r--libs/androidfw/misc.cpp69
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp27
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig10
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp16
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},