summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/coverage/tools/ExtractFlaggedApis.kt2
-rw-r--r--core/java/android/app/Notification.java2
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/view/IDisplayWindowListener.aidl5
-rw-r--r--core/java/android/window/DesktopModeFlags.java2
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserContentManager.java212
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialog.java187
-rw-r--r--core/res/res/values/config_telephony.xml5
-rw-r--r--core/tests/coretests/src/com/android/internal/app/MediaRouteChooserContentManagerTest.kt182
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskUnfoldTransitionMerger.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java25
-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/CarWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java71
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java121
-rw-r--r--media/tests/projection/Android.bp2
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java16
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java14
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant55.xml20
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_category.xml42
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night-v36/styles.xml24
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/styles.xml24
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java34
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java46
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java4
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepositoryTest.kt195
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepositoryTest.kt158
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java29
-rw-r--r--packages/SystemUI/res/drawable/animated_action_button_background.xml (renamed from packages/SystemUI/res/drawable/magic_action_button_background.xml)4
-rw-r--r--packages/SystemUI/res/layout/animated_action_button.xml17
-rw-r--r--packages/SystemUI/res/layout/magic_action_button.xml17
-rw-r--r--packages/SystemUI/res/values/colors.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml18
-rw-r--r--packages/SystemUI/src/com/android/systemui/cursorposition/data/model/CursorPosition.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepository.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/SingleDisplayCursorPositionRepository.kt154
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepository.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToDismiss.kt220
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToReveal.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionBackgroundDrawable.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionButton.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ui/compose/slider/Slider.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/cursorposition/domain/data/repository/TestCursorPositionRepositoryInstanceProvider.kt44
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/fakes/FakeLauncherApps.kt76
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt2
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java3
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java69
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java28
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java207
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java26
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java128
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java16
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowListenerController.java11
-rw-r--r--services/core/java/com/android/server/wm/Task.java15
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java82
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java100
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java70
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java20
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl10
-rw-r--r--vendor/google_testing/integration/tests/scenarios/screenshots/cuttlefish/flexiglass/six_digits_pin_delete.pngbin0 -> 43287 bytes
140 files changed, 3078 insertions, 1214 deletions
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 0a3ae4f790b0..e50f7f876f51 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -88,6 +88,6 @@ fun getFlagAnnotation(item: Item): String? {
return item.modifiers
.findAnnotation("android.annotation.FlaggedApi")
?.findAttribute("value")
- ?.value
+ ?.legacyValue
?.value() as? String
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7c293cb9cb3b..f5277fd86a57 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1983,7 +1983,7 @@ public class Notification implements Parcelable
* treatment.
* @hide
*/
- public static final String EXTRA_IS_MAGIC = "android.extra.IS_MAGIC";
+ public static final String EXTRA_IS_ANIMATED = "android.extra.IS_ANIMATED";
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index c41a5ce02e61..3dfad5396634 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -214,6 +214,13 @@ flag {
}
flag {
+ name: "request_key_capture_api"
+ namespace: "input"
+ description: "Adds support for key capture APIs"
+ bug: "375435312"
+}
+
+flag {
name: "fix_search_modifier_fallbacks"
namespace: "input"
description: "Fixes a bug in which fallbacks from Search based key combinations were not activating."
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 67ae7430e0b7..79f61f3f0d77 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -64,4 +64,9 @@ oneway interface IDisplayWindowListener {
* Called when the keep clear ares on a display have changed.
*/
void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
+
+ /**
+ * Called when the eligibility of the desktop mode for a display have changed.
+ */
+ void onDesktopModeEligibleChanged(int displayId);
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 9b3d6242b213..28a922d56019 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -148,7 +148,7 @@ public enum DesktopModeFlags {
INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true),
INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES(
- Flags::inheritTaskBoundsForTrampolineTaskLaunches, false),
+ Flags::inheritTaskBoundsForTrampolineTaskLaunches, true),
SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX(
Flags::skipDecorViewRelayoutWhenClosingBugfix, false),
// go/keep-sorted end
diff --git a/core/java/com/android/internal/app/MediaRouteChooserContentManager.java b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
index 09c6f5e6caaa..64538fdbdac1 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserContentManager.java
@@ -17,21 +17,57 @@
package com.android.internal.app;
import android.content.Context;
+import android.media.MediaRouter;
+import android.text.TextUtils;
import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
+import android.widget.TextView;
import com.android.internal.R;
+import java.util.Comparator;
+
public class MediaRouteChooserContentManager {
+ /**
+ * A delegate interface that a MediaRouteChooser UI should implement. It allows the content
+ * manager to inform the UI of any UI changes that need to be made in response to content
+ * updates.
+ */
+ public interface Delegate {
+ /**
+ * Dismiss the UI to transition to a different workflow.
+ */
+ void dismissView();
+
+ /**
+ * Returns true if the progress bar should be shown when the list view is empty.
+ */
+ boolean showProgressBarWhenEmpty();
+ }
+
Context mContext;
+ Delegate mDelegate;
- private final boolean mShowProgressBarWhenEmpty;
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
- public MediaRouteChooserContentManager(Context context, boolean showProgressBarWhenEmpty) {
+ private int mRouteTypes;
+ private RouteAdapter mAdapter;
+ private boolean mAttachedToWindow;
+
+ public MediaRouteChooserContentManager(Context context, Delegate delegate) {
mContext = context;
- mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ mDelegate = delegate;
+
+ mRouter = context.getSystemService(MediaRouter.class);
+ mCallback = new MediaRouterCallback();
+ mAdapter = new RouteAdapter(mContext);
}
/**
@@ -41,9 +77,11 @@ public class MediaRouteChooserContentManager {
public void bindViews(View containerView) {
View emptyView = containerView.findViewById(android.R.id.empty);
ListView listView = containerView.findViewById(R.id.media_route_list);
+ listView.setAdapter(mAdapter);
+ listView.setOnItemClickListener(mAdapter);
listView.setEmptyView(emptyView);
- if (!mShowProgressBarWhenEmpty) {
+ if (!mDelegate.showProgressBarWhenEmpty()) {
containerView.findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
// Center the empty view when the progress bar is not shown.
@@ -53,4 +91,170 @@ public class MediaRouteChooserContentManager {
emptyView.setLayoutParams(params);
}
}
+
+ /**
+ * Called when this UI is attached to a window..
+ */
+ public void onAttachedToWindow() {
+ mAttachedToWindow = true;
+ mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ refreshRoutes();
+ }
+
+ /**
+ * Called when this UI is detached from a window..
+ */
+ public void onDetachedFromWindow() {
+ mAttachedToWindow = false;
+ mRouter.removeCallback(mCallback);
+ }
+
+ /**
+ * Gets the media route types for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @return The route types.
+ */
+ public int getRouteTypes() {
+ return mRouteTypes;
+ }
+
+ /**
+ * Sets the types of routes that will be shown in the media route chooser dialog
+ * launched by this button.
+ *
+ * @param types The route types to match.
+ */
+ public void setRouteTypes(int types) {
+ if (mRouteTypes != types) {
+ mRouteTypes = types;
+
+ if (mAttachedToWindow) {
+ mRouter.removeCallback(mCallback);
+ mRouter.addCallback(types, mCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ }
+
+ refreshRoutes();
+ }
+ }
+
+ /**
+ * Refreshes the list of routes that are shown in the chooser dialog.
+ */
+ public void refreshRoutes() {
+ if (mAttachedToWindow) {
+ mAdapter.update();
+ }
+ }
+
+ /**
+ * Returns true if the route should be included in the list.
+ * <p>
+ * The default implementation returns true for enabled non-default routes that
+ * match the route types. Subclasses can override this method to filter routes
+ * differently.
+ * </p>
+ *
+ * @param route The route to consider, never null.
+ * @return True if the route should be included in the chooser dialog.
+ */
+ public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+ return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes);
+ }
+
+ private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+ implements AdapterView.OnItemClickListener {
+ private final LayoutInflater mInflater;
+
+ RouteAdapter(Context context) {
+ super(context, 0);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void update() {
+ clear();
+ final int count = mRouter.getRouteCount();
+ for (int i = 0; i < count; i++) {
+ MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
+ if (onFilterRoute(route)) {
+ add(route);
+ }
+ }
+ sort(RouteComparator.sInstance);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
+ }
+ MediaRouter.RouteInfo route = getItem(position);
+ TextView text1 = view.findViewById(android.R.id.text1);
+ TextView text2 = view.findViewById(android.R.id.text2);
+ text1.setText(route.getName());
+ CharSequence description = route.getDescription();
+ if (TextUtils.isEmpty(description)) {
+ text2.setVisibility(View.GONE);
+ text2.setText("");
+ } else {
+ text2.setVisibility(View.VISIBLE);
+ text2.setText(description);
+ }
+ view.setEnabled(route.isEnabled());
+ return view;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MediaRouter.RouteInfo route = getItem(position);
+ if (route.isEnabled()) {
+ route.select();
+ mDelegate.dismissView();
+ }
+ }
+ }
+
+ private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+ public static final RouteComparator sInstance = new RouteComparator();
+
+ @Override
+ public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+ return lhs.getName().toString().compareTo(rhs.getName().toString());
+ }
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
+ mDelegate.dismissView();
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
index 5030a143ea94..fc7ed89f395c 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -19,23 +19,14 @@ package com.android.internal.app;
import android.app.AlertDialog;
import android.content.Context;
import android.media.MediaRouter;
-import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
-import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
import android.widget.Button;
-import android.widget.ListView;
-import android.widget.TextView;
import com.android.internal.R;
-import java.util.Comparator;
-
/**
* This class implements the route chooser dialog for {@link MediaRouter}.
* <p>
@@ -47,15 +38,11 @@ import java.util.Comparator;
*
* TODO: Move this back into the API, as in the support library media router.
*/
-public class MediaRouteChooserDialog extends AlertDialog {
- private final MediaRouter mRouter;
- private final MediaRouterCallback mCallback;
-
- private int mRouteTypes;
+public class MediaRouteChooserDialog extends AlertDialog implements
+ MediaRouteChooserContentManager.Delegate {
private View.OnClickListener mExtendedSettingsClickListener;
- private RouteAdapter mAdapter;
private Button mExtendedSettingsButton;
- private boolean mAttachedToWindow;
+ private final boolean mShowProgressBarWhenEmpty;
private final MediaRouteChooserContentManager mContentManager;
@@ -66,19 +53,8 @@ public class MediaRouteChooserDialog extends AlertDialog {
public MediaRouteChooserDialog(Context context, int theme, boolean showProgressBarWhenEmpty) {
super(context, theme);
- mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- mCallback = new MediaRouterCallback();
- mContentManager = new MediaRouteChooserContentManager(context, showProgressBarWhenEmpty);
- }
-
- /**
- * Gets the media route types for filtering the routes that the user can
- * select using the media route chooser dialog.
- *
- * @return The route types.
- */
- public int getRouteTypes() {
- return mRouteTypes;
+ mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
+ mContentManager = new MediaRouteChooserContentManager(context, this);
}
/**
@@ -88,17 +64,7 @@ public class MediaRouteChooserDialog extends AlertDialog {
* @param types The route types to match.
*/
public void setRouteTypes(int types) {
- if (mRouteTypes != types) {
- mRouteTypes = types;
-
- if (mAttachedToWindow) {
- mRouter.removeCallback(mCallback);
- mRouter.addCallback(types, mCallback,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
-
- refreshRoutes();
- }
+ mContentManager.setRouteTypes(types);
}
public void setExtendedSettingsClickListener(View.OnClickListener listener) {
@@ -108,21 +74,6 @@ public class MediaRouteChooserDialog extends AlertDialog {
}
}
- /**
- * Returns true if the route should be included in the list.
- * <p>
- * The default implementation returns true for enabled non-default routes that
- * match the route types. Subclasses can override this method to filter routes
- * differently.
- * </p>
- *
- * @param route The route to consider, never null.
- * @return True if the route should be included in the chooser dialog.
- */
- public boolean onFilterRoute(MediaRouter.RouteInfo route) {
- return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
// Note: setView must be called before super.onCreate().
@@ -130,7 +81,7 @@ public class MediaRouteChooserDialog extends AlertDialog {
R.layout.media_route_chooser_dialog, null);
setView(containerView);
- setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
+ setTitle(mContentManager.getRouteTypes() == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
? R.string.media_route_chooser_title_for_remote_display
: R.string.media_route_chooser_title);
@@ -139,11 +90,6 @@ public class MediaRouteChooserDialog extends AlertDialog {
super.onCreate(savedInstanceState);
- mAdapter = new RouteAdapter(getContext());
- ListView listView = findViewById(R.id.media_route_list);
- listView.setAdapter(mAdapter);
- listView.setOnItemClickListener(mAdapter);
-
mExtendedSettingsButton = findViewById(R.id.media_route_extended_settings_button);
updateExtendedSettingsButton();
@@ -161,27 +107,23 @@ public class MediaRouteChooserDialog extends AlertDialog {
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
-
- mAttachedToWindow = true;
- mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- refreshRoutes();
+ mContentManager.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
- mAttachedToWindow = false;
- mRouter.removeCallback(mCallback);
-
+ mContentManager.onDetachedFromWindow();
super.onDetachedFromWindow();
}
- /**
- * Refreshes the list of routes that are shown in the chooser dialog.
- */
- public void refreshRoutes() {
- if (mAttachedToWindow) {
- mAdapter.update();
- }
+ @Override
+ public void dismissView() {
+ dismiss();
+ }
+
+ @Override
+ public boolean showProgressBarWhenEmpty() {
+ return mShowProgressBarWhenEmpty;
}
static boolean isLightTheme(Context context) {
@@ -189,99 +131,4 @@ public class MediaRouteChooserDialog extends AlertDialog {
return context.getTheme().resolveAttribute(R.attr.isLightTheme, value, true)
&& value.data != 0;
}
-
- private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
- implements ListView.OnItemClickListener {
- private final LayoutInflater mInflater;
-
- public RouteAdapter(Context context) {
- super(context, 0);
- mInflater = LayoutInflater.from(context);
- }
-
- public void update() {
- clear();
- final int count = mRouter.getRouteCount();
- for (int i = 0; i < count; i++) {
- MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
- if (onFilterRoute(route)) {
- add(route);
- }
- }
- sort(RouteComparator.sInstance);
- notifyDataSetChanged();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return getItem(position).isEnabled();
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
- if (view == null) {
- view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
- }
- MediaRouter.RouteInfo route = getItem(position);
- TextView text1 = view.findViewById(android.R.id.text1);
- TextView text2 = view.findViewById(android.R.id.text2);
- text1.setText(route.getName());
- CharSequence description = route.getDescription();
- if (TextUtils.isEmpty(description)) {
- text2.setVisibility(View.GONE);
- text2.setText("");
- } else {
- text2.setVisibility(View.VISIBLE);
- text2.setText(description);
- }
- view.setEnabled(route.isEnabled());
- return view;
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MediaRouter.RouteInfo route = getItem(position);
- if (route.isEnabled()) {
- route.select();
- dismiss();
- }
- }
- }
-
- private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
- @Override
- public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
- refreshRoutes();
- }
-
- @Override
- public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
- refreshRoutes();
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
- refreshRoutes();
- }
-
- @Override
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- dismiss();
- }
- }
-
- private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
- public static final RouteComparator sInstance = new RouteComparator();
-
- @Override
- public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
- return lhs.getName().toString().compareTo(rhs.getName().toString());
- }
- }
}
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 1d40110dc7ca..fc46418478c8 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -318,6 +318,11 @@
<bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool>
<java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" />
+ <!-- Whether the device supports disabling satellite while satellite enabling is in progress.
+ -->
+ <bool name="config_support_disable_satellite_while_enable_in_progress">true</bool>
+ <java-symbol type="bool" name="config_support_disable_satellite_while_enable_in_progress" />
+
<!-- List of country codes where oem-enabled satellite services are either allowed or disallowed
by the device. Each country code is a lowercase 2 character ISO-3166-1 alpha-2.
-->
diff --git a/core/tests/coretests/src/com/android/internal/app/MediaRouteChooserContentManagerTest.kt b/core/tests/coretests/src/com/android/internal/app/MediaRouteChooserContentManagerTest.kt
new file mode 100644
index 000000000000..bbed6e0c3618
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/MediaRouteChooserContentManagerTest.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.internal.app
+
+import android.content.Context
+import android.media.MediaRouter
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class MediaRouteChooserContentManagerTest {
+ private val context: Context = getInstrumentation().context
+
+ @Test
+ fun bindViews_showProgressBarWhenEmptyTrue_progressBarVisible() {
+ val delegate = mock<MediaRouteChooserContentManager.Delegate> {
+ on { showProgressBarWhenEmpty() } doReturn true
+ }
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val containerView = inflateMediaRouteChooserDialog()
+ contentManager.bindViews(containerView)
+
+ assertThat(containerView.findViewById<View>(R.id.media_route_progress_bar).visibility)
+ .isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun bindViews_showProgressBarWhenEmptyFalse_progressBarNotVisible() {
+ val delegate = mock<MediaRouteChooserContentManager.Delegate> {
+ on { showProgressBarWhenEmpty() } doReturn false
+ }
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val containerView = inflateMediaRouteChooserDialog()
+ contentManager.bindViews(containerView)
+ val emptyView = containerView.findViewById<View>(android.R.id.empty)
+ val emptyViewLayout = emptyView.layoutParams as? LinearLayout.LayoutParams
+
+ assertThat(containerView.findViewById<View>(R.id.media_route_progress_bar).visibility)
+ .isEqualTo(View.GONE)
+ assertThat(emptyView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyViewLayout?.gravity).isEqualTo(Gravity.CENTER)
+ }
+
+ @Test
+ fun onFilterRoute_routeDefault_returnsFalse() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val route: MediaRouter.RouteInfo = mock<MediaRouter.RouteInfo> {
+ on { isDefault } doReturn true
+ }
+
+ assertThat(contentManager.onFilterRoute(route)).isEqualTo(false)
+ }
+
+ @Test
+ fun onFilterRoute_routeNotEnabled_returnsFalse() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val route: MediaRouter.RouteInfo = mock<MediaRouter.RouteInfo> {
+ on { isEnabled } doReturn false
+ }
+
+ assertThat(contentManager.onFilterRoute(route)).isEqualTo(false)
+ }
+
+ @Test
+ fun onFilterRoute_routeNotMatch_returnsFalse() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val route: MediaRouter.RouteInfo = mock<MediaRouter.RouteInfo> {
+ on { matchesTypes(anyInt()) } doReturn false
+ }
+
+ assertThat(contentManager.onFilterRoute(route)).isEqualTo(false)
+ }
+
+ @Test
+ fun onFilterRoute_returnsTrue() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ val route: MediaRouter.RouteInfo = mock<MediaRouter.RouteInfo> {
+ on { isDefault } doReturn false
+ on { isEnabled } doReturn true
+ on { matchesTypes(anyInt()) } doReturn true
+ }
+
+ assertThat(contentManager.onFilterRoute(route)).isEqualTo(true)
+ }
+
+ @Test
+ fun onAttachedToWindow() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val mediaRouter: MediaRouter = mock()
+ val layoutInflater: LayoutInflater = mock()
+ val context: Context = mock<Context> {
+ on { getSystemServiceName(MediaRouter::class.java) } doReturn Context.MEDIA_ROUTER_SERVICE
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ on { getSystemService(Context.LAYOUT_INFLATER_SERVICE) } doReturn layoutInflater
+ }
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ contentManager.routeTypes = MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
+
+ contentManager.onAttachedToWindow()
+
+ verify(mediaRouter).addCallback(eq(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY), any(),
+ eq(MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN))
+ }
+
+ @Test
+ fun onDetachedFromWindow() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val layoutInflater: LayoutInflater = mock()
+ val mediaRouter: MediaRouter = mock()
+ val context: Context = mock<Context> {
+ on { getSystemServiceName(MediaRouter::class.java) } doReturn Context.MEDIA_ROUTER_SERVICE
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ on { getSystemService(Context.LAYOUT_INFLATER_SERVICE) } doReturn layoutInflater
+ }
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+
+ contentManager.onDetachedFromWindow()
+
+ verify(mediaRouter).removeCallback(any())
+ }
+
+ @Test
+ fun setRouteTypes() {
+ val delegate: MediaRouteChooserContentManager.Delegate = mock()
+ val mediaRouter: MediaRouter = mock()
+ val layoutInflater: LayoutInflater = mock()
+ val context: Context = mock<Context> {
+ on { getSystemServiceName(MediaRouter::class.java) } doReturn Context.MEDIA_ROUTER_SERVICE
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ on { getSystemService(Context.LAYOUT_INFLATER_SERVICE) } doReturn layoutInflater
+ }
+ val contentManager = MediaRouteChooserContentManager(context, delegate)
+ contentManager.onAttachedToWindow()
+
+ contentManager.routeTypes = MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
+
+ assertThat(contentManager.routeTypes).isEqualTo(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)
+ verify(mediaRouter).addCallback(eq(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY), any(),
+ eq(MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN))
+ }
+
+ private fun inflateMediaRouteChooserDialog(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.media_route_chooser_dialog, null, false)
+ }
+}
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 70fa48cca0b0..6a7b5cc0e1ba 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
@@ -81,6 +81,7 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
+import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -156,7 +157,7 @@ import java.util.function.IntConsumer;
*/
public class BubbleController implements ConfigurationChangeListener,
RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider,
- BubbleBarDragListener {
+ BubbleBarDragListener, BubbleTaskUnfoldTransitionMerger {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -2175,6 +2176,32 @@ public class BubbleController implements ConfigurationChangeListener,
});
}
+ @Override
+ public boolean mergeTaskWithUnfold(@NonNull ActivityManager.RunningTaskInfo taskInfo,
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT) {
+ if (!mBubbleTransitions.mTaskViewTransitions.isTaskViewTask(taskInfo)) {
+ // if this task isn't managed by bubble transitions just bail.
+ return false;
+ }
+ if (isShowingAsBubbleBar()) {
+ // if bubble bar is enabled, the task view will switch to a new surface on unfold, so we
+ // should not merge the transition.
+ return false;
+ }
+
+ boolean merged = mBubbleTransitions.mTaskViewTransitions.updateBoundsForUnfold(
+ change.getEndAbsBounds(), startT, finishT, change.getTaskInfo(), change.getLeash());
+ if (merged) {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (selectedBubble != null && selectedBubble.getExpandedView() != null) {
+ selectedBubble.getExpandedView().onContainerClipUpdate();
+ }
+ }
+ return merged;
+ }
+
/** When bubbles are floating, this will be used to notify the floating views. */
private final BubbleViewCallback mBubbleStackViewCallback = new BubbleViewCallback() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 290ef1633819..ac8393576477 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -843,7 +843,8 @@ public class BubbleExpandedView extends LinearLayout {
onContainerClipUpdate();
}
- private void onContainerClipUpdate() {
+ /** Updates the clip bounds. */
+ public void onContainerClipUpdate() {
if (mTopClip == 0 && mBottomClip == 0 && mRightClip == 0 && mLeftClip == 0) {
if (mIsClipping) {
mIsClipping = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskUnfoldTransitionMerger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskUnfoldTransitionMerger.kt
new file mode 100644
index 000000000000..13fabc8b1d91
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskUnfoldTransitionMerger.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.bubbles
+
+import android.app.ActivityManager
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+
+/** Merges a bubble task transition with the unfold transition. */
+interface BubbleTaskUnfoldTransitionMerger {
+
+ /** Attempts to merge the transition. Returns `true` if the change was merged. */
+ fun mergeTaskWithUnfold(
+ taskInfo: ActivityManager.RunningTaskInfo,
+ change: TransitionInfo.Change,
+ startT: SurfaceControl.Transaction,
+ finishT: SurfaceControl.Transaction
+ ): Boolean
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 46f6e40ec5e8..06d734c71f6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -355,6 +355,19 @@ public class DisplayController {
}
}
+ private void onDesktopModeEligibleChanged(int displayId) {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onDesktopModeEligibleChanged on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onDesktopModeEligibleChanged(displayId);
+ }
+ }
+ }
+
private static class DisplayRecord {
private int mDisplayId;
private Context mContext;
@@ -422,6 +435,13 @@ public class DisplayController {
new ArraySet<>(restricted), new ArraySet<>(unrestricted));
});
}
+
+ @Override
+ public void onDesktopModeEligibleChanged(int displayId) {
+ mMainExecutor.execute(() -> {
+ DisplayController.this.onDesktopModeEligibleChanged(displayId);
+ });
+ }
}
/**
@@ -467,5 +487,10 @@ public class DisplayController {
* Called when the display topology has changed.
*/
default void onTopologyChanged(DisplayTopology topology) {}
+
+ /**
+ * Called when the eligibility of the desktop mode for a display have changed.
+ */
+ default void onDesktopModeEligibleChanged(int displayId) {}
}
}
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 5fbbb0bf1e78..07745d487f64 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
@@ -62,6 +62,7 @@ import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleResizabilityChecker;
+import com.android.wm.shell.bubbles.BubbleTaskUnfoldTransitionMerger;
import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
import com.android.wm.shell.common.DisplayController;
@@ -244,6 +245,13 @@ public abstract class WMShellModule {
context, logger, positioner, educationController, mainExecutor, bgExecutor);
}
+ @WMSingleton
+ @Provides
+ static Optional<BubbleTaskUnfoldTransitionMerger> provideBubbleTaskUnfoldTransitionMerger(
+ Optional<BubbleController> bubbleController) {
+ return bubbleController.map(controller -> controller);
+ }
+
// Note: Handler needed for LauncherApps.register
@WMSingleton
@Provides
@@ -705,7 +713,8 @@ public abstract class WMShellModule {
Transitions transitions,
@ShellMainThread ShellExecutor executor,
@ShellMainThread Handler handler,
- ShellInit shellInit) {
+ ShellInit shellInit,
+ Optional<BubbleTaskUnfoldTransitionMerger> bubbleTaskUnfoldTransitionMerger) {
return new UnfoldTransitionHandler(
shellInit,
progressProvider.get(),
@@ -714,7 +723,8 @@ public abstract class WMShellModule {
transactionPool,
executor,
handler,
- transitions);
+ transitions,
+ bubbleTaskUnfoldTransitionMerger);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index 3b98f8123b46..25737c4950d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -89,6 +89,15 @@ class DesktopDisplayEventHandler(
// TODO: b/362720497 - move desks in closing display to the remaining desk.
}
+ override fun onDesktopModeEligibleChanged(displayId: Int) {
+ if (
+ DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue &&
+ displayId != DEFAULT_DISPLAY
+ ) {
+ desktopDisplayModeController.refreshDisplayWindowingMode()
+ }
+ }
+
override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
if (remainingDesks == 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 3c44fe8061aa..55179511af6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -22,7 +22,6 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.TaskInfo
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ActivityInfo.LAUNCH_MULTIPLE
import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE
import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK
@@ -303,21 +302,19 @@ fun getInheritedExistingTaskBounds(
// Top task is an instance of launching activity. Activity will be launching in a new
// task with the existing task also being closed. Inherit existing task bounds to
// prevent new task jumping.
- (isLaunchingNewTask(launchMode, intentFlags) && isClosingExitingInstance(intentFlags)) ->
+ (isLaunchingNewSingleTask(launchMode) && isClosingExitingInstance(intentFlags)) ->
lastTask.configuration.windowConfiguration.bounds
else -> null
}
}
/**
- * Returns true if the launch mode or intent will result in a new task being created for the
- * activity.
+ * Returns true if the launch mode will result in a single new task being created for the activity.
*/
-private fun isLaunchingNewTask(launchMode: Int, intentFlags: Int) =
+private fun isLaunchingNewSingleTask(launchMode: Int) =
launchMode == LAUNCH_SINGLE_TASK ||
launchMode == LAUNCH_SINGLE_INSTANCE ||
- launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK ||
- (intentFlags and FLAG_ACTIVITY_NEW_TASK) != 0
+ launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
/**
* Returns true if the intent will result in an existing task instance being closed if a new one
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 1e5514407ee5..b60fd5bb6c73 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
@@ -449,6 +449,11 @@ class DesktopTasksController(
return false
}
+ // Secondary displays are always desktop-first
+ if (displayId != DEFAULT_DISPLAY) {
+ return true
+ }
+
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
// A non-organized display (e.g., non-trusted virtual displays used in CTS) doesn't have
// TDA.
@@ -1140,6 +1145,7 @@ class DesktopTasksController(
}
val t =
if (remoteTransition == null) {
+ logV("startLaunchTransition -- no remoteTransition -- wct = $launchTransaction")
desktopMixedTransitionHandler.startLaunchTransition(
transitionType = transitionType,
wct = launchTransaction,
@@ -2793,11 +2799,14 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
deskId: Int?,
): RunOnTransitStart? {
- // This windowing mode is to get the transition animation started; once we complete
- // split select, we will change windowing mode to undefined and inherit from split stage.
- // Going to undefined here causes task to flicker to the top left.
- // Cancelling the split select flow will revert it to fullscreen.
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ if (!DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue) {
+ // This windowing mode is to get the transition animation started; once we complete
+ // split select, we will change windowing mode to undefined and inherit from split
+ // stage.
+ // Going to undefined here causes task to flicker to the top left.
+ // Cancelling the split select flow will revert it to fullscreen.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ }
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index e7492f17835a..2476ee1a4090 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -174,7 +174,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
SurfaceControl.Transaction finishT) {
mTaskChangeListener.ifPresent(listener -> listener.onTaskChanging(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onToFrontTransitionReady(
@@ -184,7 +184,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToFront(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onToBackTransitionReady(
@@ -194,7 +194,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
mTaskChangeListener.ifPresent(
listener -> listener.onTaskMovingToBack(change.getTaskInfo()));
mWindowDecorViewModel.onTaskChanging(
- change.getTaskInfo(), change.getLeash(), startT, finishT, change.getMode());
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 014c810d1e5f..10db5ca03637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -124,7 +124,6 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.DesktopExperienceFlags;
-import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -684,8 +683,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!enteredSplitSelect) {
return null;
}
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()
- && !DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
mTaskOrganizer.applyTransaction(wct);
return null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index a6f872634ee9..22848c38bb1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -728,6 +728,48 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
taskView.notifyAppeared(newTask);
}
+ /**
+ * Updates bounds for the task view during an unfold transition.
+ *
+ * @return true if the task was found and a transition for this task is pending. false
+ * otherwise.
+ */
+ public boolean updateBoundsForUnfold(Rect bounds, SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction,
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ final TaskViewTaskController taskView = findTaskView(taskInfo);
+ if (taskView == null) {
+ return false;
+ }
+
+ final PendingTransition pendingTransition = findPending(taskView, TRANSIT_CHANGE);
+ if (pendingTransition == null) {
+ return false;
+ }
+
+ mPending.remove(pendingTransition);
+
+ // reparent the task under the task view surface and set the bounds on it
+ startTransaction.reparent(leash, taskView.getSurfaceControl())
+ .setPosition(leash, 0, 0)
+ .setWindowCrop(leash, bounds.width(), bounds.height())
+ .show(leash);
+ // the finish transaction would reparent the task back to the transition root, so reparent
+ // it again to the task view surface
+ finishTransaction.reparent(leash, taskView.getSurfaceControl())
+ .setPosition(leash, 0, 0)
+ .setWindowCrop(leash, bounds.width(), bounds.height());
+ if (useRepo()) {
+ final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+ if (state != null) {
+ state.mBounds.set(bounds);
+ }
+ } else {
+ updateBoundsState(taskView, bounds);
+ }
+ return true;
+ }
+
private void updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 7fd19a7d2a88..706a366441cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.bubbles.BubbleTaskUnfoldTransitionMerger;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
@@ -53,6 +54,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
/**
@@ -80,6 +82,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
private final Transitions mTransitions;
+ private final Optional<BubbleTaskUnfoldTransitionMerger> mBubbleTaskUnfoldTransitionMerger;
private final Executor mExecutor;
private final TransactionPool mTransactionPool;
private final Handler mHandler;
@@ -108,12 +111,14 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
TransactionPool transactionPool,
Executor executor,
Handler handler,
- Transitions transitions) {
+ Transitions transitions,
+ Optional<BubbleTaskUnfoldTransitionMerger> bubbleTaskUnfoldTransitionMerger) {
mUnfoldProgressProvider = unfoldProgressProvider;
mTransitions = transitions;
mTransactionPool = transactionPool;
mExecutor = executor;
mHandler = handler;
+ mBubbleTaskUnfoldTransitionMerger = bubbleTaskUnfoldTransitionMerger;
mAnimators.add(splitUnfoldTaskAnimator);
mAnimators.add(fullscreenUnfoldAnimator);
@@ -237,14 +242,26 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
}
// TODO (b/286928742) unfold transition handler should be part of mixed handler to
// handle merges better.
+
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null
&& taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
- // Tasks that are always on top (e.g. bubbles), will handle their own transition
- // as they are on top of everything else. So skip merging transitions here.
- return;
+ // Tasks that are always on top, excluding bubbles, will handle their own transition
+ // as they are on top of everything else. If this is a transition for a bubble task,
+ // attempt to merge it. Otherwise skip merging transitions.
+ if (mBubbleTaskUnfoldTransitionMerger.isPresent()) {
+ boolean merged =
+ mBubbleTaskUnfoldTransitionMerger
+ .get()
+ .mergeTaskWithUnfold(taskInfo, change, startT, finishT);
+ if (!merged) {
+ return;
+ }
+ } else {
+ return;
+ }
}
}
// Apply changes happening during the unfold animation immediately
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 42321e56e72b..7871179a50de 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
@@ -49,7 +49,6 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.window.DisplayAreaInfo;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -234,8 +233,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @TransitionInfo.TransitionMode int changeMode) {
+ SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 4511fbe10764..2b2cdf84005c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -31,7 +31,6 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -160,8 +159,7 @@ public abstract class CarWindowDecorViewModel
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @TransitionInfo.TransitionMode int changeMode) {
+ SurfaceControl.Transaction finishT) {
final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
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 69e1f36dec0b..0082d7971ad2 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
@@ -27,7 +27,6 @@ import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
@@ -80,7 +79,6 @@ import android.view.ViewConfiguration;
import android.view.ViewRootImpl;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -602,8 +600,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @TransitionInfo.TransitionMode int changeMode) {
+ SurfaceControl.Transaction finishT) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (!shouldShowWindowDecor(taskInfo)) {
if (decoration != null) {
@@ -617,8 +614,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion,
- /*isMovingToBack= */ changeMode == TRANSIT_TO_BACK);
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
}
@@ -633,7 +629,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion, /* isMovingToBack= */ false);
+ mExclusionRegion);
}
@Override
@@ -1892,7 +1888,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo),
- mExclusionRegion, /* isMovingToBack= */ false);
+ mExclusionRegion);
if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 50bc7b5e865b..d24308137936 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -217,7 +217,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private boolean mIsDragging = false;
private Runnable mLoadAppInfoRunnable;
private Runnable mSetAppInfoRunnable;
- private boolean mIsMovingToBack;
public DesktopModeWindowDecoration(
Context context,
@@ -479,7 +478,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus, displayExclusionRegion, mIsMovingToBack);
+ hasGlobalFocus, displayExclusionRegion);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -506,8 +505,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus, @NonNull Region displayExclusionRegion,
- boolean isMovingToBack) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
@@ -530,7 +528,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
.isTaskInFullImmersiveState(taskInfo.taskId);
- mIsMovingToBack = isMovingToBack;
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
@@ -539,8 +536,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
/* shouldIgnoreCornerRadius= */ mIsRecentsTransitionRunning
&& DesktopModeFlags
.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(),
- mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo),
- mIsRecentsTransitionRunning, mIsMovingToBack);
+ mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo));
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -633,6 +629,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mBgExecutor.execute(mLoadAppInfoRunnable);
}
+ private boolean showInputLayer() {
+ if (!DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
+ return isCaptionVisible();
+ }
+ // Don't show the input layer during the recents transition, otherwise it could become
+ // touchable while in overview, during quick-switch or even for a short moment after going
+ // Home.
+ return isCaptionVisible() && !mIsRecentsTransitionRunning;
+ }
+
private boolean isCaptionVisible() {
return mTaskInfo.isVisible && mIsCaptionVisible;
}
@@ -874,7 +880,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (!isAppHandle(mWindowDecorViewHolder)) return;
asAppHandle(mWindowDecorViewHolder).bindData(new AppHandleViewHolder.HandleData(
mTaskInfo, determineHandlePosition(), mResult.mCaptionWidth,
- mResult.mCaptionHeight, /* showInputLayer= */ isCaptionVisible(),
+ mResult.mCaptionHeight, /* showInputLayer= */ showInputLayer(),
/* isCaptionVisible= */ isCaptionVisible()
));
}
@@ -959,9 +965,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion,
boolean shouldIgnoreCornerRadius,
- boolean shouldExcludeCaptionFromAppBounds,
- boolean isRecentsTransitionRunning,
- boolean isMovingToBack) {
+ boolean shouldExcludeCaptionFromAppBounds) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -979,19 +983,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mAsyncViewHost = isAppHandle;
boolean showCaption;
- // If this relayout is occurring from an observed TRANSIT_TO_BACK transition, do not
- // show caption (this includes split select transition).
- if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
- && isMovingToBack && !isDragging) {
- showCaption = false;
- } else if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
// If the task is being dragged, the caption should not be hidden so that it continues
// receiving input
showCaption = true;
- } else if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()
- && isRecentsTransitionRunning) {
- // Caption should not be visible in recents.
- showCaption = false;
} else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
if (inFullImmersiveMode) {
showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
@@ -1895,18 +1890,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* <p> When a Recents transition is active we allow that transition to take ownership of the
* corner radius of its task surfaces, so each window decoration should stop updating the corner
* radius of its task surface during that time.
- *
- * We should not allow input to reach the input layer during a Recents transition, so
- * update the handle view holder accordingly if transition status changes.
*/
void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
- if (mIsRecentsTransitionRunning != isRecentsTransitionRunning) {
- mIsRecentsTransitionRunning = isRecentsTransitionRunning;
- if (DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
- // We don't relayout decor on recents transition, so we need to call it directly.
- relayout(mTaskInfo, mHasGlobalFocus, mRelayoutParams.mDisplayExclusionRegion);
- }
- }
+ mIsRecentsTransitionRunning = isRecentsTransitionRunning;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 5e4a0a5860f0..1563259f4a1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -84,14 +83,12 @@ public interface WindowDecorViewModel {
* @param taskSurface the surface of the task
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
- * @param changeMode the type of change to the task
*/
void onTaskChanging(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @TransitionInfo.TransitionMode int changeMode);
+ SurfaceControl.Transaction finishT);
/**
* Notifies that the given task is about to close to give the window decoration a chance to
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
index 68f7ef09ee70..f9b69d3f5f7e 100644
--- a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
@@ -41,7 +41,6 @@ import org.junit.runners.model.Statement
class SimulatedConnectedDisplayTestRule : TestRule {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
- private val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
private val displayManager = context.getSystemService(DisplayManager::class.java)
private val addedDisplays = mutableListOf<Int>()
@@ -102,7 +101,8 @@ class SimulatedConnectedDisplayTestRule : TestRule {
// Add the overlay displays
Settings.Global.putString(
InstrumentationRegistry.getInstrumentation().context.contentResolver,
- Settings.Global.OVERLAY_DISPLAY_DEVICES, displaySettings
+ Settings.Global.OVERLAY_DISPLAY_DEVICES,
+ displaySettings
)
withTimeoutOrNull(TIMEOUT) {
displayAddedFlow.take(displays.size).collect { displayId ->
@@ -125,10 +125,6 @@ class SimulatedConnectedDisplayTestRule : TestRule {
}
private fun cleanupTestDisplays() = runBlocking {
- if (addedDisplays.isEmpty()) {
- return@runBlocking
- }
-
val displayRemovedFlow: Flow<Int> = callbackFlow {
val listener = object : DisplayListener {
override fun onDisplayAdded(displayId: Int) {}
@@ -146,16 +142,24 @@ class SimulatedConnectedDisplayTestRule : TestRule {
}
}
- // Remove overlay displays
+ // Remove overlay displays. We'll execute this regardless of addedDisplays just to
+ // ensure all overlay displays are removed before and after the test.
+ // Note: If we want to restore the original overlay display added before this test (and its
+ // topology), it will be complicated as re-adding overlay display would lead to different
+ // displayId and topology could not be restored easily.
Settings.Global.putString(
InstrumentationRegistry.getInstrumentation().context.contentResolver,
- Settings.Global.OVERLAY_DISPLAY_DEVICES, null)
+ Settings.Global.OVERLAY_DISPLAY_DEVICES,
+ null
+ )
- withTimeoutOrNull(TIMEOUT) {
- displayRemovedFlow.take(addedDisplays.size).collect { displayId ->
- addedDisplays.remove(displayId)
- }
- } ?: error("Timed out waiting for displays to be removed.")
+ if (!addedDisplays.isEmpty()) {
+ withTimeoutOrNull(TIMEOUT) {
+ displayRemovedFlow.take(addedDisplays.size).collect { displayId ->
+ addedDisplays.remove(displayId)
+ }
+ } ?: error("Timed out waiting for displays to be removed: $addedDisplays")
+ }
}
private companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 9268db60aa51..85a431be8e8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.server.display.feature.flags.Flags as DisplayFlags
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
@@ -246,6 +247,13 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
verify(desktopDisplayModeController).refreshDisplayWindowingMode()
}
+ @Test
+ @EnableFlags(DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ fun testDesktopModeEligibleChanged() {
+ onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId)
+ verify(desktopDisplayModeController).refreshDisplayWindowingMode()
+ }
+
private class FakeDesktopRepositoryInitializer : DesktopRepositoryInitializer {
override var deskRecreationFactory: DesktopRepositoryInitializer.DeskRecreationFactory =
DesktopRepositoryInitializer.DeskRecreationFactory { _, _, deskId -> deskId }
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 d495f76e7814..b8c2273e1465 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
@@ -32,9 +32,9 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.CONFIG_DENSITY
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
@@ -1200,9 +1200,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
existingTask.topActivity = testComponent
existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
// Set up new instance of already existing task.
- val launchingTask = setUpFullscreenTask()
+ val launchingTask =
+ setUpFullscreenTask().apply {
+ topActivityInfo = ActivityInfo().apply { launchMode = LAUNCH_SINGLE_INSTANCE }
+ }
launchingTask.topActivity = testComponent
- launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
// Move new instance to desktop. By default multi instance is not supported so first
// instance will close.
@@ -1224,10 +1226,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
existingTask.topActivity = testComponent
existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
// Set up new instance of already existing task.
- val launchingTask = setUpFreeformTask(active = false)
+ val launchingTask =
+ setUpFreeformTask(active = false).apply {
+ topActivityInfo = ActivityInfo().apply { launchMode = LAUNCH_SINGLE_INSTANCE }
+ }
taskRepository.removeTask(launchingTask.displayId, launchingTask.taskId)
launchingTask.topActivity = testComponent
- launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK)
// Move new instance to desktop. By default multi instance is not supported so first
// instance will close.
@@ -4283,6 +4287,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_secondaryDisplay_movesToDesk() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = SECONDARY_DISPLAY_ID, deskId = deskId)
+ taskRepository.setDeskInactive(deskId)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+
+ val fullscreenTask = createFullscreenTask(displayId = SECONDARY_DISPLAY_ID)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
fun handleRequest_fullscreenTask_notInDesk_enforceDesktop_fullscreenDisplay_returnNull() {
taskRepository.setDeskInactive(deskId = 0)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index f6e49853eddf..82373ff1bc41 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -571,8 +571,7 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- @DisableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX})
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
public void testRequestEnterSplit_didNotEnterSplitSelect_doesNotApplyTransaction() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mStageCoordinator.registerSplitSelectListener(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 3a455ba6b5df..f11839ad4e72 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -24,8 +24,12 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -77,16 +81,15 @@ public class TaskViewTransitionsTest extends ShellTestCase {
@Mock
TaskViewTaskController mTaskViewTaskController;
@Mock
- ActivityManager.RunningTaskInfo mTaskInfo;
- @Mock
WindowContainerToken mToken;
@Mock
ShellTaskOrganizer mOrganizer;
@Mock
SyncTransactionQueue mSyncQueue;
- Executor mExecutor = command -> command.run();
+ Executor mExecutor = Runnable::run;
+ ActivityManager.RunningTaskInfo mTaskInfo;
TaskViewRepository mTaskViewRepository;
TaskViewTransitions mTaskViewTransitions;
@@ -305,4 +308,66 @@ public class TaskViewTransitionsTest extends ShellTestCase {
verify(mTaskViewTaskController).setTaskNotFound();
}
+
+ @Test
+ public void updateBoundsForUnfold_taskNotFound_doesNothing() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.token = mock(WindowContainerToken.class);
+ taskInfo.taskId = 666;
+ Rect bounds = new Rect(100, 50, 200, 250);
+ SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishTransaction = mock(SurfaceControl.Transaction.class);
+ assertThat(
+ mTaskViewTransitions.updateBoundsForUnfold(bounds, startTransaction,
+ finishTransaction, taskInfo, mock(SurfaceControl.class)))
+ .isFalse();
+
+ verify(startTransaction, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void updateBoundsForUnfold_noPendingTransition_doesNothing() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ Rect bounds = new Rect(100, 50, 200, 250);
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, bounds);
+ assertThat(mTaskViewTransitions.hasPending()).isFalse();
+
+ SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishTransaction = mock(SurfaceControl.Transaction.class);
+ assertThat(
+ mTaskViewTransitions.updateBoundsForUnfold(bounds, startTransaction,
+ finishTransaction, mTaskInfo, mock(SurfaceControl.class)))
+ .isFalse();
+ verify(startTransaction, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void updateBoundsForUnfold() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ Rect bounds = new Rect(100, 50, 200, 250);
+ mTaskViewTransitions.updateVisibilityState(mTaskViewTaskController, /* visible= */ true);
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController, bounds);
+ assertThat(mTaskViewTransitions.hasPending()).isTrue();
+
+ SurfaceControl.Transaction startTransaction = createMockTransaction();
+ SurfaceControl.Transaction finishTransaction = createMockTransaction();
+ assertThat(
+ mTaskViewTransitions.updateBoundsForUnfold(bounds, startTransaction,
+ finishTransaction, mTaskInfo, mock(SurfaceControl.class)))
+ .isTrue();
+ assertThat(mTaskViewRepository.byTaskView(mTaskViewTaskController).mBounds)
+ .isEqualTo(bounds);
+ }
+
+ private SurfaceControl.Transaction createMockTransaction() {
+ SurfaceControl.Transaction transaction = mock(SurfaceControl.Transaction.class);
+ when(transaction.reparent(any(), any())).thenReturn(transaction);
+ when(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction);
+ when(transaction.setWindowCrop(any(), anyInt(), anyInt())).thenReturn(transaction);
+ return transaction;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index aad18cba4436..e28d0acb579f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -60,6 +60,7 @@ import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
public class UnfoldTransitionHandlerTest extends ShellTestCase {
@@ -98,7 +99,8 @@ public class UnfoldTransitionHandlerTest extends ShellTestCase {
mTransactionPool,
executor,
mHandler,
- mTransitions
+ mTransitions,
+ /* bubbleTaskUnfoldTransitionMerger= */ Optional.empty()
);
shellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index b1f92411c5a3..067dcec5d65d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -28,7 +28,6 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
-import android.view.WindowManager.TRANSIT_CHANGE
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -110,7 +109,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
onTaskOpening(task, taskSurface)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
+ onTaskChanging(task, taskSurface)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -166,7 +165,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
setLargeScreen(false)
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
+ onTaskChanging(task, taskSurface)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
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 ad3426e82805..40aa41b2b72a 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
@@ -51,7 +51,6 @@ import android.view.SurfaceView
import android.view.View
import android.view.ViewRootImpl
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
import androidx.test.filters.SmallTest
@@ -135,7 +134,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
task.setActivityType(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
+ onTaskChanging(task, taskSurface)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
verify(decoration).close()
@@ -150,12 +149,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
val taskSurface = SurfaceControl()
setUpMockDecorationForTask(task)
- onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
+ onTaskChanging(task, taskSurface)
assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
task.setActivityType(ACTIVITY_TYPE_STANDARD)
- onTaskChanging(task, taskSurface, TRANSIT_CHANGE)
+ onTaskChanging(task, taskSurface)
assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
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 2126d1d9b986..80dcd7d69f00 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
@@ -362,14 +362,12 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected fun onTaskChanging(
task: RunningTaskInfo,
leash: SurfaceControl = SurfaceControl(),
- changeMode: Int
) {
desktopModeWindowDecorViewModel.onTaskChanging(
task,
leash,
StubTransaction(),
StubTransaction(),
- changeMode
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 0908f56e1cfb..a0171ea04da3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -174,8 +174,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
private static final boolean DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS = false;
- private static final boolean DEFAULT_IS_RECENTS_TRANSITION_RUNNING = false;
- private static final boolean DEFAULT_IS_MOVING_TO_BACK = false;
@Mock
@@ -443,9 +441,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
/* shouldIgnoreCornerRadius= */ true,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
}
@@ -544,9 +540,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
/* shouldIgnoreCornerRadius= */ true,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
}
@@ -748,9 +742,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- /* shouldExcludeCaptionFromAppBounds */ true,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ /* shouldExcludeCaptionFromAppBounds */ true);
// Force consuming flags are disabled.
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
@@ -785,9 +777,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
assertThat(
@@ -866,9 +856,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -896,9 +884,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -925,9 +911,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -954,9 +938,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -982,9 +964,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1010,9 +990,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1039,9 +1017,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -1060,9 +1036,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1089,9 +1063,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -1118,9 +1090,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -1151,65 +1121,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
assertThat(relayoutParams.mAsyncViewHost).isFalse();
}
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
- public void updateRelayoutParams_handle_movingToBack_captionNotVisible() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final RelayoutParams relayoutParams = new RelayoutParams();
-
- DesktopModeWindowDecoration.updateRelayoutParams(
- relayoutParams,
- mTestableContext,
- taskInfo,
- mMockSplitScreenController,
- DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
- DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
- DEFAULT_IS_STATUSBAR_VISIBLE,
- DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
- DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
- DEFAULT_IS_DRAGGING,
- new InsetsState(),
- DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion,
- DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- /* isMovingToBack= */ true);
-
- assertThat(relayoutParams.mIsCaptionVisible).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_INPUT_LAYER_TRANSITION_FIX)
- public void updateRelayoutParams_handle_inRecentsTransition_captionNotVisible() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final RelayoutParams relayoutParams = new RelayoutParams();
-
- DesktopModeWindowDecoration.updateRelayoutParams(
- relayoutParams,
- mTestableContext,
- taskInfo,
- mMockSplitScreenController,
- DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
- DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
- DEFAULT_IS_STATUSBAR_VISIBLE,
- DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
- DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
- DEFAULT_IS_DRAGGING,
- new InsetsState(),
- DEFAULT_HAS_GLOBAL_FOCUS,
- mExclusionRegion,
- DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- /* isRecentsTransitionRunning= */ true,
- DEFAULT_IS_MOVING_TO_BACK);
-
- assertThat(relayoutParams.mIsCaptionVisible).isFalse();
- }
-
@Test
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1846,9 +1757,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
DEFAULT_SHOULD_IGNORE_CORNER_RADIUS,
- DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
- DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
- DEFAULT_IS_MOVING_TO_BACK);
+ DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS);
}
private DesktopModeWindowDecoration createWindowDecoration(
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 48621e4e2094..0b02d3cb4250 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -3,7 +3,7 @@
//########################################################################
package {
- default_team: "trendy_team_lse_desktop_os_experience",
+ default_team: "trendy_team_media_projection",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 964268e4ad14..518757dd0d5c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -359,11 +359,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
if (CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
// If the scan times out, do NOT close the activity automatically and let the
// user manually cancel the flow.
- synchronized (LOCK) {
- if (sDiscoveryStarted) {
- stopDiscovery();
- }
- }
+ stopDiscovery();
mTimeoutMessage.setText(getString(R.string.message_discovery_hard_timeout));
mTimeoutMessage.setVisibility(View.VISIBLE);
}
@@ -455,8 +451,14 @@ public class CompanionAssociationActivity extends FragmentActivity implements
}
private void stopDiscovery() {
- if (mRequest != null && !mRequest.isSelfManaged()) {
- CompanionDeviceDiscoveryService.stop(this);
+ if (mRequest == null || mRequest.isSelfManaged()) {
+ return;
+ }
+
+ synchronized (LOCK) {
+ if (sDiscoveryStarted) {
+ CompanionDeviceDiscoveryService.stop(this);
+ }
}
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 50a01b3bc7c9..7b4794506adb 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -136,7 +136,12 @@ public class CompanionDeviceDiscoveryService extends Service {
intent.setAction(ACTION_START_DISCOVERY);
intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
- context.startService(intent);
+ try {
+ context.startService(intent);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to start discovery.", e);
+ return false;
+ }
return true;
}
@@ -144,7 +149,12 @@ public class CompanionDeviceDiscoveryService extends Service {
static void stop(@NonNull Context context) {
final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
intent.setAction(ACTION_STOP_DISCOVERY);
- context.startService(intent);
+
+ try {
+ context.startService(intent);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to stop discovery.", e);
+ }
}
static LiveData<List<DeviceFilterPair<?>>> getScanResult() {
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant55.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant55.xml
new file mode 100644
index 000000000000..53ffa234f432
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant55.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">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="55"/>
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_category.xml b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_category.xml
new file mode 100644
index 000000000000..44b8e7c96a88
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v36/settingslib_expressive_preference_category.xml
@@ -0,0 +1,42 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:baselineAligned="false"
+ android:layout_marginTop="@dimen/settingslib_expressive_space_small1"
+ android:gravity="center_vertical"
+ android:filterTouchesWhenObscured="false">
+
+ <TextView
+ android:id="@android:id/title"
+ android:paddingStart="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingBottom="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ style="@style/PreferenceCategoryTitleTextStyle"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v36/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v36/styles.xml
new file mode 100644
index 000000000000..ca99d449b6b3
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v36/styles.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<resources>
+ <style name="Seekbar.SettingsLib" parent="@android:style/Widget.Material.SeekBar">
+ <item name="android:thumbTint">@android:color/system_accent1_100</item>
+ <item name="android:progressTint">@android:color/system_accent1_100</item>
+ <item name="android:progressBackgroundTint">@android:color/system_neutral2_500</item>
+ <item name="android:progressBackgroundTintMode">src_over</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles.xml
new file mode 100644
index 000000000000..a31983a04753
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/styles.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<resources>
+ <style name="Seekbar.SettingsLib" parent="@android:style/Widget.Material.SeekBar">
+ <item name="android:thumbTint">@android:color/system_accent1_800</item>
+ <item name="android:progressTint">@android:color/system_accent1_800</item>
+ <item name="android:progressBackgroundTint">@color/settingslib_neutral_variant55</item>
+ <item name="android:progressBackgroundTintMode">src_over</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml
index cec8e45e2bfb..ec6fe887d31e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_preference_expressive.xml
@@ -40,6 +40,7 @@
</style>
<style name="SettingsLibPreference.Category.Expressive">
+ <item name="layout">@layout/settingslib_expressive_preference_category</item>
</style>
<style name="SettingsLibPreference.CheckBoxPreference.Expressive">
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
index 1c45ff6ca6cf..54bd069f2fc3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/themes.xml
@@ -22,6 +22,7 @@
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
+ <item name="android:seekBarStyle">@style/Seekbar.SettingsLib</item>
<!-- Set up edge-to-edge configuration for top app bar -->
<item name="android:clipToPadding">false</item>
<item name="android:clipChildren">false</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e81c8220d707..78b307e7b816 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -25,6 +25,7 @@ import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
@@ -58,6 +59,7 @@ import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
+import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -397,6 +399,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
Log.d(TAG, "Disconnect " + this);
+ if (Flags.enableLeAudioSharing()) {
+ removeBroadcastSource(ImmutableSet.of(mDevice));
+ }
mDevice.disconnect();
}
// Disconnect PBAP server in case its connected
@@ -609,6 +614,16 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
final BluetoothDevice dev = mDevice;
if (dev != null) {
mUnpairing = true;
+ if (Flags.enableLeAudioSharing()) {
+ Set<BluetoothDevice> devicesToRemoveSource = new HashSet<>();
+ devicesToRemoveSource.add(dev);
+ if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ for (CachedBluetoothDevice member : getMemberDevice()) {
+ devicesToRemoveSource.add(member.getDevice());
+ }
+ }
+ removeBroadcastSource(devicesToRemoveSource);
+ }
final boolean successful = dev.removeBond();
if (successful) {
releaseLruCache();
@@ -623,6 +638,25 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
+ @WorkerThread
+ private void removeBroadcastSource(Set<BluetoothDevice> devices) {
+ if (mProfileManager == null || devices.isEmpty()) return;
+ LocalBluetoothLeBroadcast broadcast = mProfileManager.getLeAudioBroadcastProfile();
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mProfileManager.getLeAudioBroadcastAssistantProfile();
+ if (broadcast != null && assistant != null && broadcast.isEnabled(null)) {
+ for (BluetoothDevice device : devices) {
+ for (BluetoothLeBroadcastReceiveState state : assistant.getAllSources(device)) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "Remove broadcast source " + state.getBroadcastId()
+ + " from device " + device.getAnonymizedAddress());
+ }
+ assistant.removeSource(device, state.getSourceId());
+ }
+ }
+ }
+ }
+
public int getProfileConnectionState(LocalBluetoothProfile profile) {
return profile != null
? profile.getConnectionStatus(mDevice)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 635010ec824d..146b66737e83 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -2383,6 +2383,52 @@ public class CachedBluetoothDeviceTest {
Integer.parseInt(TWS_BATTERY_LEFT));
}
+ @Test
+ public void disconnect_removeBroadcastSource() {
+ when(mCachedDevice.getGroupId()).thenReturn(1);
+ when(mSubCachedDevice.getGroupId()).thenReturn(1);
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ LocalBluetoothLeBroadcastAssistant assistant = mock(
+ LocalBluetoothLeBroadcastAssistant.class);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(broadcast.isEnabled(null)).thenReturn(true);
+ BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+ when(state.getSourceId()).thenReturn(1);
+ when(assistant.getAllSources(mDevice)).thenReturn(ImmutableList.of(state));
+ when(assistant.getAllSources(mSubDevice)).thenReturn(ImmutableList.of(state));
+
+ mCachedDevice.disconnect();
+ verify(assistant).removeSource(mDevice, /* sourceId= */1);
+ verify(assistant).removeSource(mSubDevice, /* sourceId= */1);
+ verify(mDevice).disconnect();
+ verify(mSubDevice).disconnect();
+ }
+
+ @Test
+ public void unpair_removeBroadcastSource() {
+ when(mCachedDevice.getGroupId()).thenReturn(1);
+ when(mSubCachedDevice.getGroupId()).thenReturn(1);
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ LocalBluetoothLeBroadcastAssistant assistant = mock(
+ LocalBluetoothLeBroadcastAssistant.class);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(broadcast.isEnabled(null)).thenReturn(true);
+ BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+ when(state.getSourceId()).thenReturn(1);
+ when(assistant.getAllSources(mDevice)).thenReturn(ImmutableList.of(state));
+ when(assistant.getAllSources(mSubDevice)).thenReturn(ImmutableList.of(state));
+
+ mCachedDevice.unpair();
+ verify(assistant).removeSource(mDevice, /* sourceId= */1);
+ verify(assistant).removeSource(mSubDevice, /* sourceId= */1);
+ verify(mDevice).removeBond();
+ }
+
private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
doReturn(status).when(profile).getConnectionStatus(mDevice);
mCachedDevice.onProfileStateChanged(profile, status);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index fd3f18d4724c..a93291f2db98 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2001,6 +2001,13 @@ flag {
}
flag {
+ name: "notification_animated_actions_treatment"
+ namespace: "systemui"
+ description: "Special UI treatment for animated actions and replys"
+ bug: "383567383"
+}
+
+flag {
name: "show_audio_sharing_slider_in_volume_panel"
namespace: "cross_device_experiences"
description: "Show two sliders in volume panel when audio sharing."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index 060f0c94732d..9e08317d2c6b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -199,7 +199,9 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
info.releaseAllSurfaces();
// Make sure that the transition leashes created are not leaked.
for (SurfaceControl leash : leashMap.values()) {
- finishTransaction.reparent(leash, null);
+ if (leash.isValid()) {
+ finishTransaction.reparent(leash, null);
+ }
}
// Don't release here since launcher might still be using them. Instead
// let launcher release them (eg. via RemoteAnimationTargets)
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index 07a571b94ce4..c411d272cb22 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
+/** Returns a [remember]ed [OffsetOverscrollEffect]. */
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun rememberOffsetOverscrollEffect(
@@ -63,7 +64,10 @@ data class OffsetOverscrollEffectFactory(
private val animationSpec: AnimationSpec<Float>,
) : OverscrollFactory {
override fun createOverscrollEffect(): OverscrollEffect {
- return OffsetOverscrollEffect(animationScope, animationSpec)
+ return OffsetOverscrollEffect(
+ animationScope = animationScope,
+ animationSpec = animationSpec,
+ )
}
}
@@ -80,11 +84,11 @@ class OffsetOverscrollEffect(animationScope: CoroutineScope, animationSpec: Anim
return layout(placeable.width, placeable.height) {
val offsetPx = computeOffset(density = this@measure, overscrollDistance)
if (offsetPx != 0) {
- placeable.placeRelativeWithLayer(
+ placeable.placeWithLayer(
with(requireConverter()) { offsetPx.toIntOffset() }
)
} else {
- placeable.placeRelative(0, 0)
+ placeable.place(0, 0)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 8f0fb20cef36..cb03119d1e19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -39,6 +39,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
@@ -266,7 +267,10 @@ fun ContentScope.QuickSettingsLayout(
BrightnessSliderContainer(
viewModel = viewModel.brightnessSliderViewModel,
containerColors =
- ContainerColors.singleColor(OverlayShade.Colors.PanelBackground),
+ ContainerColors(
+ idleColor = Color.Transparent,
+ mirrorColor = OverlayShade.Colors.PanelBackground,
+ ),
modifier = Modifier.fillMaxWidth(),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
index dc5891915bfc..d1cbeca2070c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.session.shared
+import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -39,6 +40,9 @@ class SessionStorage {
/** Clears the data store; any downstream usage within `@Composable`s will be recomposed. */
fun clear() {
+ for (storageEntry in _storage.values) {
+ (storageEntry.stored as? RememberObserver)?.onForgotten()
+ }
_storage = hashMapOf()
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index b30e12f073ad..89c54bcc1ced 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -37,7 +37,6 @@ import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.overscroll
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
@@ -46,6 +45,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -57,6 +57,8 @@ import com.android.mechanics.behavior.VerticalExpandContainerSpec
import com.android.mechanics.behavior.verticalExpandContainerBackground
import com.android.systemui.Flags
import com.android.systemui.res.R
+import com.android.systemui.shade.ui.ShadeColors.notificationScrim
+import com.android.systemui.shade.ui.ShadeColors.shadePanel
import com.android.systemui.shade.ui.composable.OverlayShade.rememberShadeExpansionMotion
/** Renders a lightweight shade UI container, as an overlay. */
@@ -190,17 +192,15 @@ object OverlayShade {
}
object Colors {
- val ScrimBackground = Color(0f, 0f, 0f, alpha = 0.3f)
+ val ScrimBackground: Color
+ @Composable
+ @ReadOnlyComposable
+ get() = Color(LocalResources.current.notificationScrim(Flags.notificationShadeBlur()))
+
val PanelBackground: Color
@Composable
@ReadOnlyComposable
- get() {
- return if (Flags.notificationShadeBlur()) {
- MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f)
- } else {
- MaterialTheme.colorScheme.surfaceContainer
- }
- }
+ get() = Color(LocalResources.current.shadePanel(Flags.notificationShadeBlur()))
}
object Dimensions {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepositoryTest.kt
new file mode 100644
index 000000000000..5609e8b7604c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepositoryTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.systemui.cursorposition.data.repository
+
+import android.os.Handler
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.cursorposition.data.model.CursorPosition
+import com.android.systemui.cursorposition.data.repository.SingleDisplayCursorPositionRepositoryImpl.Companion.defaultInputEventListenerBuilder
+import com.android.systemui.cursorposition.domain.data.repository.TestCursorPositionRepositoryInstanceProvider
+import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fakeDisplayInstanceLifecycleManager
+import com.android.systemui.display.data.repository.perDisplayDumpHelper
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.launch
+import org.junit.Before
+import org.junit.Rule
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class MultiDisplayCursorPositionRepositoryTest(private val cursorEventSource: Int) :
+ SysuiTestCase() {
+
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private lateinit var underTest: MultiDisplayCursorPositionRepository
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val displayRepository = kosmos.displayRepository
+ private val displayLifecycleManager = kosmos.fakeDisplayInstanceLifecycleManager
+
+ private lateinit var listener: InputChannelCompat.InputEventListener
+ private var emittedCursorPosition: CursorPosition? = null
+
+ @Mock private lateinit var inputMonitor: InputMonitorCompat
+ @Mock private lateinit var inputReceiver: InputChannelCompat.InputEventReceiver
+
+ private val x = 100f
+ private val y = 200f
+
+ private lateinit var testableLooper: TestableLooper
+
+ @Before
+ fun setup() {
+ testableLooper = TestableLooper.get(this)
+ val testHandler = Handler(testableLooper.looper)
+ whenever(inputMonitor.getInputReceiver(any(), any(), any())).thenReturn(inputReceiver)
+ displayLifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY, DISPLAY_2)
+
+ val cursorPerDisplayRepository =
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "testCursorPositionPerDisplayInstanceRepository",
+ instanceProvider =
+ TestCursorPositionRepositoryInstanceProvider(
+ testHandler,
+ { channel ->
+ listener = defaultInputEventListenerBuilder.build(channel)
+ listener
+ },
+ ) { _: String, _: Int ->
+ inputMonitor
+ },
+ displayLifecycleManager,
+ kosmos.backgroundScope,
+ displayRepository,
+ kosmos.perDisplayDumpHelper,
+ )
+
+ underTest =
+ MultiDisplayCursorPositionRepositoryImpl(
+ displayRepository,
+ backgroundScope = kosmos.backgroundScope,
+ cursorPerDisplayRepository,
+ )
+ }
+
+ @Test
+ fun getCursorPositionFromDefaultDisplay() = setUpAndRunTest {
+ val event = getMotionEvent(x, y, 0)
+ listener.onInputEvent(event)
+
+ assertThat(emittedCursorPosition).isEqualTo(CursorPosition(x, y, 0))
+ }
+
+ @Test
+ fun getCursorPositionFromAdditionDisplay() = setUpAndRunTest {
+ addDisplay(id = DISPLAY_2, type = TYPE_EXTERNAL)
+
+ val event = getMotionEvent(x, y, DISPLAY_2)
+ listener.onInputEvent(event)
+
+ assertThat(emittedCursorPosition).isEqualTo(CursorPosition(x, y, DISPLAY_2))
+ }
+
+ @Test
+ fun noCursorPositionFromRemovedDisplay() = setUpAndRunTest {
+ addDisplay(id = DISPLAY_2, type = TYPE_EXTERNAL)
+ removeDisplay(DISPLAY_2)
+
+ val event = getMotionEvent(x, y, DISPLAY_2)
+ listener.onInputEvent(event)
+
+ assertThat(emittedCursorPosition).isEqualTo(null)
+ }
+
+ @Test
+ fun disposeInputMonitorAndInputReceiver() = setUpAndRunTest {
+ addDisplay(DISPLAY_2, TYPE_EXTERNAL)
+ removeDisplay(DISPLAY_2)
+
+ verify(inputMonitor).dispose()
+ verify(inputReceiver).dispose()
+ }
+
+ private fun setUpAndRunTest(block: suspend () -> Unit) =
+ kosmos.runTest {
+ // Add default display before creating cursor repository
+ displayRepository.addDisplays(display(id = DEFAULT_DISPLAY, type = TYPE_INTERNAL))
+
+ backgroundScope.launch {
+ underTest.cursorPositions.collect { emittedCursorPosition = it }
+ }
+ // Run all tasks received by TestHandler to create input monitors
+ testableLooper.processAllMessages()
+
+ block()
+ }
+
+ private suspend fun addDisplay(id: Int, type: Int) {
+ displayRepository.addDisplays(display(id = id, type = type))
+ testableLooper.processAllMessages()
+ }
+
+ private suspend fun removeDisplay(id: Int) {
+ displayRepository.removeDisplay(id)
+ testableLooper.processAllMessages()
+ }
+
+ private fun getMotionEvent(x: Float, y: Float, displayId: Int): MotionEvent {
+ val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, x, y, 0)
+ event.source = cursorEventSource
+ event.displayId = displayId
+ return event
+ }
+
+ private companion object {
+ const val DISPLAY_2 = DEFAULT_DISPLAY + 1
+
+ @JvmStatic
+ @Parameters(name = "source = {0}")
+ fun data(): List<Int> {
+ return listOf(SOURCE_MOUSE, SOURCE_TOUCHPAD)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepositoryTest.kt
new file mode 100644
index 000000000000..a2e42976f413
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepositoryTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.systemui.keyboard.shortcut.data.repository
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.fakeLauncherApps
+import com.android.systemui.keyboard.shortcut.userVisibleAppsRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserVisibleAppsRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val fakeLauncherApps = kosmos.fakeLauncherApps
+ private val repo = kosmos.userVisibleAppsRepository
+ private val userTracker = kosmos.fakeUserTracker
+ private val testScope = kosmos.testScope
+ private val userVisibleAppsContainsApplication:
+ (pkgName: String, clsName: String) -> Flow<Boolean> =
+ { pkgName, clsName ->
+ repo.userVisibleApps.map { userVisibleApps ->
+ userVisibleApps.any {
+ it.componentName.packageName == pkgName && it.componentName.className == clsName
+ }
+ }
+ }
+
+ @Before
+ fun setup() {
+ switchUser(index = PRIMARY_USER_INDEX)
+ }
+
+ @Test
+ fun userVisibleApps_emitsUpdatedAppsList_onNewAppInstalled() {
+ testScope.runTest {
+ val containsPackageOne by
+ collectLastValue(userVisibleAppsContainsApplication(TEST_PACKAGE_1, TEST_CLASS_1))
+
+ installPackageOneForUserOne()
+
+ assertThat(containsPackageOne).isTrue()
+ }
+ }
+
+ @Test
+ fun userVisibleApps_emitsUpdatedAppsList_onAppUserChanged() {
+ testScope.runTest {
+ val containsPackageOne by
+ collectLastValue(userVisibleAppsContainsApplication(TEST_PACKAGE_1, TEST_CLASS_1))
+ val containsPackageTwo by
+ collectLastValue(userVisibleAppsContainsApplication(TEST_PACKAGE_2, TEST_CLASS_2))
+
+ installPackageOneForUserOne()
+ installPackageTwoForUserTwo()
+
+ assertThat(containsPackageOne).isTrue()
+ assertThat(containsPackageTwo).isFalse()
+
+ switchUser(index = SECONDARY_USER_INDEX)
+
+ assertThat(containsPackageOne).isFalse()
+ assertThat(containsPackageTwo).isTrue()
+ }
+ }
+
+ @Test
+ fun userVisibleApps_emitsUpdatedAppsList_onAppUninstalled() {
+ testScope.runTest {
+ val containsPackageOne by
+ collectLastValue(userVisibleAppsContainsApplication(TEST_PACKAGE_1, TEST_CLASS_1))
+
+ installPackageOneForUserOne()
+ uninstallPackageOneForUserOne()
+
+ assertThat(containsPackageOne).isFalse()
+ }
+ }
+
+ private fun switchUser(index: Int) {
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(/* id= */ PRIMARY_USER_ID, /* name= */ "Primary User", /* flags= */ 0),
+ UserInfo(
+ /* id= */ SECONDARY_USER_ID,
+ /* name= */ "Secondary User",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = index,
+ )
+ }
+
+ private fun installPackageOneForUserOne() {
+ fakeLauncherApps.installPackageForUser(
+ TEST_PACKAGE_1,
+ TEST_CLASS_1,
+ UserHandle(/* userId= */ PRIMARY_USER_ID),
+ )
+ }
+
+ private fun uninstallPackageOneForUserOne() {
+ fakeLauncherApps.uninstallPackageForUser(
+ TEST_PACKAGE_1,
+ TEST_CLASS_1,
+ UserHandle(/* userId= */ PRIMARY_USER_ID),
+ )
+ }
+
+ private fun installPackageTwoForUserTwo() {
+ fakeLauncherApps.installPackageForUser(
+ TEST_PACKAGE_2,
+ TEST_CLASS_2,
+ UserHandle(/* userId= */ SECONDARY_USER_ID),
+ )
+ }
+
+ companion object {
+ const val TEST_PACKAGE_1 = "test.package.one"
+ const val TEST_PACKAGE_2 = "test.package.two"
+ const val TEST_CLASS_1 = "TestClassOne"
+ const val TEST_CLASS_2 = "TestClassTwo"
+ const val PRIMARY_USER_ID = 10
+ const val PRIMARY_USER_INDEX = 0
+ const val SECONDARY_USER_ID = 11
+ const val SECONDARY_USER_INDEX = 1
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 676e1ea5321a..579c242f974a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -41,16 +41,12 @@ import static org.mockito.Mockito.when;
import android.animation.Animator;
import android.annotation.IdRes;
-import android.content.ContentResolver;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
-import android.os.PowerManager;
-import android.os.UserManager;
import android.util.DisplayMetrics;
import android.view.Display;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -65,10 +61,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardSliceViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
-import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -142,7 +136,6 @@ import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -164,9 +157,7 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
-import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -214,7 +205,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected DozeParameters mDozeParameters;
@Mock protected ScreenOffAnimationController mScreenOffAnimationController;
@Mock protected NotificationPanelView mView;
- @Mock protected LayoutInflater mLayoutInflater;
@Mock protected DynamicPrivacyController mDynamicPrivacyController;
@Mock protected ShadeTouchableRegionManager mShadeTouchableRegionManager;
@Mock protected KeyguardStateController mKeyguardStateController;
@@ -223,7 +213,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected CommandQueue mCommandQueue;
@Mock protected VibratorHelper mVibratorHelper;
@Mock protected LatencyTracker mLatencyTracker;
- @Mock protected PowerManager mPowerManager;
@Mock protected AccessibilityManager mAccessibilityManager;
@Mock protected MetricsLogger mMetricsLogger;
@Mock protected Resources mResources;
@@ -242,14 +231,12 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected ScrimController mScrimController;
@Mock protected MediaDataManager mMediaDataManager;
@Mock protected AmbientState mAmbientState;
- @Mock protected UserManager mUserManager;
@Mock protected UiEventLogger mUiEventLogger;
@Mock protected KeyguardMediaController mKeyguardMediaController;
@Mock protected NavigationModeController mNavigationModeController;
@Mock protected NavigationBarController mNavigationBarController;
@Mock protected QuickSettingsControllerImpl mQsController;
@Mock protected ShadeHeaderController mShadeHeaderController;
- @Mock protected ContentResolver mContentResolver;
@Mock protected TapAgainViewController mTapAgainViewController;
@Mock protected KeyguardIndicationController mKeyguardIndicationController;
@Mock protected FragmentService mFragmentService;
@@ -261,12 +248,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected DumpManager mDumpManager;
@Mock protected NotificationsQSContainerController mNotificationsQSContainerController;
@Mock protected QsFrameTranslateController mQsFrameTranslateController;
- @Mock protected StatusBarWindowStateController mStatusBarWindowStateController;
@Mock protected KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock protected NotificationShadeWindowController mNotificationShadeWindowController;
@Mock protected SysUiState mSysUiState;
@Mock protected NotificationListContainer mNotificationListContainer;
- @Mock protected NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock protected QS mQs;
@Mock protected QSFragmentLegacy mQSFragment;
@@ -281,8 +266,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock protected MotionEvent mDownMotionEvent;
@Mock protected CoroutineDispatcher mMainDispatcher;
- @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
- private final KeyguardLogger mKeyguardLogger = new KeyguardLogger(logcatLogBuffer());
@Captor
protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
mEmptySpaceClickListenerCaptor;
@@ -363,9 +346,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
- final SplitShadeStateController splitShadeStateController =
- new ResourcesSplitShadeStateController();
-
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
mKosmos.getDeviceProvisioningInteractor(),
@@ -380,8 +360,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
mFakeKeyguardRepository,
mShadeRepository
- ),
- mKosmos.getShadeModeInteractor());
+ ));
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(
mUiEventLogger,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index db0c07c50dc6..348eee8de313 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -199,8 +199,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
mKeyguardRepository,
mShadeRepository
- ),
- mKosmos.getShadeModeInteractor());
+ ));
when(mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance)).thenReturn(DEFAULT_HEIGHT);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 67af7a54988e..039a32ba9127 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -353,7 +353,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.addListener(listener)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(anyFloat())
- verify(listener).onWallpaperZoomOutChanged(anyFloat())
verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
}
@@ -369,7 +368,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(eq(0f))
- verify(listener).onWallpaperZoomOutChanged(eq(0f))
}
@Test
@@ -384,7 +382,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(floatThat { it != 0f })
- verify(listener).onWallpaperZoomOutChanged(floatThat { it != 0f })
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
index 0caddf46cd3a..d56890dc5d3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
@@ -137,9 +137,28 @@ class ConversationNotificationProcessorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ fun processNotification_messagingStyleUpdateSummarizationToNull() {
+ val nb = getMessagingNotification()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+ newRow.entry.setRanking(
+ RankingBuilder(newRow.entry.ranking).setSummarization("hello").build()
+ )
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNotNull()
+
+ newRow.entry.setRanking(RankingBuilder(newRow.entry.ranking).setSummarization(null).build())
+
+ assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
+ .isNotNull()
+ assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
fun processNotification_messagingStyleWithoutSummarization() {
val nb = getMessagingNotification()
val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
+
assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
.isNotNull()
assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
index 0bb473721446..c22b03cc1a1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
@@ -207,6 +207,21 @@ class PhysicsPropertyAnimatorTest : SysuiTestCase() {
}
@Test
+ fun testCancelAnimationResetsOffset() {
+ PhysicsPropertyAnimator.setProperty(
+ view,
+ property,
+ 200f,
+ animationProperties,
+ true,
+ finishListener,
+ )
+ val propertyData = ViewState.getChildTag(view, property.tag) as PropertyData
+ propertyData.animator?.cancel()
+ Assert.assertTrue(propertyData.offset == 0f)
+ }
+
+ @Test
fun testUsingListenerProperties() {
val finishListener2 = Mockito.mock(DynamicAnimation.OnAnimationEndListener::class.java)
val animationProperties: AnimationProperties =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index b3d678b1fda6..247c66aebad7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification.GROUP_ALERT_ALL
import android.app.Notification.GROUP_ALERT_SUMMARY
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
import android.platform.test.annotations.EnableFlags
+import android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -50,6 +52,7 @@ import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinde
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
@@ -61,7 +64,6 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
-import java.util.ArrayList
import java.util.function.Consumer
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -105,6 +107,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
private val notifPipeline: NotifPipeline = mock()
private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
+ private val interruptLogger: VisualInterruptionDecisionLogger = mock()
private val headsUpManager: HeadsUpManagerImpl = mock()
private val headsUpViewBinder: HeadsUpViewBinder = mock()
private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
@@ -135,6 +138,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
HeadsUpCoordinator(
kosmos.applicationCoroutineScope,
logger,
+ interruptLogger,
systemClock,
notifCollection,
headsUpManager,
@@ -920,6 +924,48 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
assertFalse(groupSummary.hasInterrupted())
}
+ private fun helpTestNoTransferToBundleChildForChannel(channelId: String) {
+ // Set up for normal alert transfer from summary to child
+ // but here child is classified so it should not happen
+ val bundleChild =
+ helper.createClassifiedEntry(/* isSummary= */ false, GROUP_ALERT_SUMMARY, channelId);
+ setShouldHeadsUp(bundleChild, true)
+ setShouldHeadsUp(groupSummary, true)
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, bundleChild))
+
+ collectionListener.onEntryAdded(groupSummary)
+ collectionListener.onEntryAdded(bundleChild)
+
+ beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupSummary, bundleChild))
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupSummary, bundleChild))
+
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any())
+ verify(headsUpViewBinder, never()).bindHeadsUpView(eq(bundleChild), any(), any())
+
+ verify(headsUpManager, never()).showNotification(groupSummary)
+ verify(headsUpManager, never()).showNotification(bundleChild)
+
+ // Capture last param
+ val decision = withArgCaptor {
+ verify(interruptLogger)
+ .logDecision(capture(), capture(), capture())
+ }
+ assertFalse(decision.shouldInterrupt)
+ assertEquals(decision.logReason, "disqualified-transfer-target")
+
+ // Must clear invocations, otherwise these calls get stored for the next call from the same
+ // test, which complains that there are more invocations than expected
+ clearInvocations(interruptLogger)
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ fun testNoTransfer_toBundleChild() {
+ for (id in SYSTEM_RESERVED_IDS) {
+ helpTestNoTransferToBundleChildForChannel(id)
+ }
+ }
+
@Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index d61fc05c699f..28ca891b2771 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.logging;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
@@ -49,6 +50,11 @@ public class NotificationPanelLoggerFake implements NotificationPanelLogger {
public void logNotificationDrag(NotificationEntry draggedNotification) {
}
+ @Override
+ public void logNotificationDrag(EntryAdapter draggedNotification) {
+
+ }
+
public static class CallRecord {
public boolean isLockscreen;
public Notifications.NotificationList list;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index fd49f60e7ae1..bbff9cf2f616 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -40,9 +40,12 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import org.junit.Before;
import org.junit.Test;
@@ -99,7 +102,13 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
mRow.doDragCallback(0, 0);
verify(controller).startDragAndDrop(mRow);
verify(mHeadsUpManager, times(1)).releaseAllImmediately();
- verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
+ if (NotificationBundleUi.isEnabled()) {
+ verify(mNotificationPanelLogger, times(1))
+ .logNotificationDrag(any(EntryAdapter.class));
+ } else {
+ verify(mNotificationPanelLogger, times(1))
+ .logNotificationDrag(any(NotificationEntry.class));
+ }
}
@Test
@@ -111,7 +120,13 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
verify(controller).startDragAndDrop(mRow);
verify(mShadeController).animateCollapseShade(eq(0), eq(true),
eq(false), anyFloat());
- verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
+ if (NotificationBundleUi.isEnabled()) {
+ verify(mNotificationPanelLogger, times(1))
+ .logNotificationDrag(any(EntryAdapter.class));
+ } else {
+ verify(mNotificationPanelLogger, times(1))
+ .logNotificationDrag(any(NotificationEntry.class));
+ }
}
@Test
@@ -129,8 +144,13 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
// Verify that we never start the actual drag since there is no content
verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt());
- verify(mNotificationPanelLogger, never()).logNotificationDrag(any());
- }
+ if (NotificationBundleUi.isEnabled()) {
+ verify(mNotificationPanelLogger, never())
+ .logNotificationDrag(any(EntryAdapter.class));
+ } else {
+ verify(mNotificationPanelLogger, never())
+ .logNotificationDrag(any(NotificationEntry.class));
+ } }
private ExpandableNotificationRowDragController createSpyController() {
return spy(mController);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 19e98387a120..533b7a6a6acf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -23,6 +24,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.content.Context;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -85,6 +87,33 @@ public final class NotificationGroupTestHelper {
return entry;
}
+ public NotificationEntry createClassifiedEntry(boolean isSummary,
+ int groupAlertBehavior, String channelId) {
+
+ Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentTitle("Title")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupAlertBehavior(groupAlertBehavior)
+ .setGroupSummary(isSummary)
+ .setGroup(TEST_GROUP_ID)
+ .build();
+
+ NotificationChannel channel = new NotificationChannel(channelId, channelId, IMPORTANCE_LOW);
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setId(mId++)
+ .setNotification(notif)
+ .updateRanking((rankingBuilder -> rankingBuilder.setChannel(channel)))
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ entry.setRow(row);
+ when(row.getEntryLegacy()).thenReturn(entry);
+ return entry;
+ }
+
public NotificationEntry createEntry(int id, String tag, boolean isSummary,
int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
diff --git a/packages/SystemUI/res/drawable/magic_action_button_background.xml b/packages/SystemUI/res/drawable/animated_action_button_background.xml
index 7199b2dfbe5a..1cecec535ad9 100644
--- a/packages/SystemUI/res/drawable/magic_action_button_background.xml
+++ b/packages/SystemUI/res/drawable/animated_action_button_background.xml
@@ -10,10 +10,10 @@
android:insetRight="0dp"
android:insetTop="8dp">
<shape android:shape="rectangle">
- <corners android:radius="@dimen/magic_action_button_corner_radius" />
+ <corners android:radius="@dimen/animated_action_button_corner_radius" />
<solid android:color="@androidprv:color/materialColorPrimaryContainer" />
<stroke
- android:width="@dimen/magic_action_button_outline_stroke_width"
+ android:width="@dimen/animated_action_button_outline_stroke_width"
android:color="@androidprv:color/materialColorOutlineVariant" />
</shape>
</inset>
diff --git a/packages/SystemUI/res/layout/animated_action_button.xml b/packages/SystemUI/res/layout/animated_action_button.xml
new file mode 100644
index 000000000000..3e5e35b815e1
--- /dev/null
+++ b/packages/SystemUI/res/layout/animated_action_button.xml
@@ -0,0 +1,17 @@
+<com.android.systemui.statusbar.notification.row.AnimatedActionButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@android:style/Widget.Material.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/animated_action_button_touch_target_height"
+ android:background="@drawable/animated_action_button_background"
+ android:drawablePadding="@dimen/animated_action_button_drawable_padding"
+ android:ellipsize="none"
+ android:fontFamily="google-sans-flex-medium"
+ android:gravity="center"
+ android:minWidth="0dp"
+ android:paddingHorizontal="@dimen/animated_action_button_padding_horizontal"
+ android:paddingVertical="@dimen/animated_action_button_inset_vertical"
+ android:stateListAnimator="@null"
+ android:textColor="@color/animated_action_button_text_color"
+ android:textSize="@dimen/animated_action_button_font_size"
+ android:textStyle="normal" />
diff --git a/packages/SystemUI/res/layout/magic_action_button.xml b/packages/SystemUI/res/layout/magic_action_button.xml
deleted file mode 100644
index 63fc1e485635..000000000000
--- a/packages/SystemUI/res/layout/magic_action_button.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<com.android.systemui.statusbar.notification.row.MagicActionButton
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="@android:style/Widget.Material.Button"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/magic_action_button_touch_target_height"
- android:background="@drawable/magic_action_button_background"
- android:drawablePadding="@dimen/magic_action_button_drawable_padding"
- android:ellipsize="none"
- android:fontFamily="google-sans-flex-medium"
- android:gravity="center"
- android:minWidth="0dp"
- android:paddingHorizontal="@dimen/magic_action_button_padding_horizontal"
- android:paddingVertical="@dimen/magic_action_button_inset_vertical"
- android:stateListAnimator="@null"
- android:textColor="@color/magic_action_button_text_color"
- android:textSize="@dimen/magic_action_button_font_size"
- android:textStyle="normal" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 7c6a1b1bf63d..ff16e063f5b1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -145,9 +145,9 @@
<color name="smart_reply_button_background">#ffffffff</color>
<color name="smart_reply_button_stroke">@*android:color/accent_device_default</color>
- <!-- Magic Action colors -->
- <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurface</color>
- <color name="magic_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color>
+ <!-- Animated Action colors -->
+ <color name="animated_action_button_text_color">@androidprv:color/materialColorOnSurface</color>
+ <color name="animated_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color>
<!-- Biometric dialog colors -->
<color name="biometric_dialog_gray">#ff757575</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 55e94028b95e..4dfb8cdf7920 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1160,16 +1160,16 @@
<dimen name="notification_2025_smart_reply_button_corner_radius">18dp</dimen>
<dimen name="notification_2025_smart_reply_button_min_height">48dp</dimen>
- <!-- Magic Action params. -->
+ <!-- Animated Action params. -->
<!-- Corner radius = half of min_height to create rounded sides. -->
- <dimen name="magic_action_button_corner_radius">16dp</dimen>
- <dimen name="magic_action_button_icon_size">20dp</dimen>
- <dimen name="magic_action_button_outline_stroke_width">1dp</dimen>
- <dimen name="magic_action_button_padding_horizontal">12dp</dimen>
- <dimen name="magic_action_button_inset_vertical">8dp</dimen>
- <dimen name="magic_action_button_drawable_padding">8dp</dimen>
- <dimen name="magic_action_button_touch_target_height">48dp</dimen>
- <dimen name="magic_action_button_font_size">12sp</dimen>
+ <dimen name="animated_action_button_corner_radius">16dp</dimen>
+ <dimen name="animated_action_button_icon_size">20dp</dimen>
+ <dimen name="animated_action_button_outline_stroke_width">1dp</dimen>
+ <dimen name="animated_action_button_padding_horizontal">12dp</dimen>
+ <dimen name="animated_action_button_inset_vertical">8dp</dimen>
+ <dimen name="animated_action_button_drawable_padding">8dp</dimen>
+ <dimen name="animated_action_button_touch_target_height">48dp</dimen>
+ <dimen name="animated_action_button_font_size">12sp</dimen>
<!-- A reasonable upper bound for the height of the smart reply button. The measuring code
needs to start with a guess for the maximum size. Currently two-line smart reply buttons
diff --git a/packages/SystemUI/src/com/android/systemui/cursorposition/data/model/CursorPosition.kt b/packages/SystemUI/src/com/android/systemui/cursorposition/data/model/CursorPosition.kt
new file mode 100644
index 000000000000..65174cc41028
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cursorposition/data/model/CursorPosition.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.cursorposition.data.model
+
+/**
+ * Represents the position of cursor hotspot on the screen. Hotspot is the specific pixel that
+ * signifies the location of the pointer's interaction with the user interface. By default, hotspot
+ * of a cursor is the tip of arrow.
+ *
+ * @property x The x-coordinate of the cursor hotspot, relative to the top-left corner of the
+ * screen.
+ * @property y The y-coordinate of the cursor hotspot, relative to the top-left corner of the
+ * screen.
+ * @property displayId The display on which the cursor is located.
+ */
+data class CursorPosition(val x: Float, val y: Float, val displayId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepository.kt b/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepository.kt
new file mode 100644
index 000000000000..37f4a4c87114
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/MultiDisplayCursorPositionRepository.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.systemui.cursorposition.data.repository
+
+import com.android.app.displaylib.PerDisplayRepository
+import com.android.systemui.cursorposition.data.model.CursorPosition
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapMerge
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository for cursor position of multi displays. */
+interface MultiDisplayCursorPositionRepository {
+ val cursorPositions: Flow<CursorPosition?>
+}
+
+/**
+ * Implementation of [MultiDisplayCursorPositionRepository] that aggregates cursor position updates
+ * from multiple displays.
+ *
+ * This class uses a [DisplayRepository] to track added displays and a [PerDisplayRepository] to
+ * manage [SingleDisplayCursorPositionRepository] instances for each display. [PerDisplayRepository]
+ * would destroy the instance if the display is removed. This class combines the cursor position
+ * from all displays into a single cursorPositions StateFlow.
+ */
+@SysUISingleton
+class MultiDisplayCursorPositionRepositoryImpl
+@Inject
+constructor(
+ private val displayRepository: DisplayRepository,
+ @Background private val backgroundScope: CoroutineScope,
+ private val cursorRepositories: PerDisplayRepository<SingleDisplayCursorPositionRepository>,
+) : MultiDisplayCursorPositionRepository {
+
+ private val allDisplaysCursorPositions: Flow<CursorPosition> =
+ displayRepository.displayAdditionEvent
+ .mapNotNull { c -> c?.displayId }
+ .onStart { emitAll(displayRepository.displayIds.value.asFlow()) }
+ .flatMapMerge {
+ val repo = cursorRepositories[it]
+ repo?.cursorPositions ?: emptyFlow()
+ }
+
+ override val cursorPositions: StateFlow<CursorPosition?> =
+ allDisplaysCursorPositions.stateIn(backgroundScope, SharingStarted.WhileSubscribed(), null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/SingleDisplayCursorPositionRepository.kt b/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/SingleDisplayCursorPositionRepository.kt
new file mode 100644
index 000000000000..f532fb22a19c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cursorposition/data/repository/SingleDisplayCursorPositionRepository.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.systemui.cursorposition.data.repository
+
+import android.os.Handler
+import android.os.Looper
+import android.view.Choreographer
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import android.view.MotionEvent
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.cursorposition.data.model.CursorPosition
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver
+import com.android.systemui.shared.system.InputMonitorCompat
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Inject
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+
+/** Repository for cursor position in single display. */
+interface SingleDisplayCursorPositionRepository {
+ /** Flow of [CursorPosition] for the display. */
+ val cursorPositions: Flow<CursorPosition>
+
+ /** Destroys the repository. */
+ fun destroy()
+}
+
+/**
+ * Implementation of [SingleDisplayCursorPositionRepository].
+ *
+ * @param displayId the display id
+ * @param backgroundHandler the background handler
+ * @param listenerBuilder the builder for [InputChannelCompat.InputEventListener]
+ * @param inputMonitorBuilder the builder for [InputMonitorCompat]
+ */
+class SingleDisplayCursorPositionRepositoryImpl
+@AssistedInject
+constructor(
+ @Assisted displayId: Int,
+ @Background private val backgroundHandler: Handler,
+ @Assisted
+ private val listenerBuilder: InputEventListenerBuilder = defaultInputEventListenerBuilder,
+ @Assisted private val inputMonitorBuilder: InputMonitorBuilder = defaultInputMonitorBuilder,
+) : SingleDisplayCursorPositionRepository {
+
+ private var scope: ProducerScope<CursorPosition>? = null
+
+ private fun createInputMonitorCallbackFlow(displayId: Int): Flow<CursorPosition> =
+ conflatedCallbackFlow {
+ val inputMonitor: InputMonitorCompat = inputMonitorBuilder.build(TAG, displayId)
+ val inputReceiver: InputEventReceiver =
+ inputMonitor.getInputReceiver(
+ Looper.myLooper(),
+ Choreographer.getInstance(),
+ listenerBuilder.build(this),
+ )
+ scope = this
+ awaitClose {
+ inputMonitor.dispose()
+ inputReceiver.dispose()
+ }
+ }
+ // Use backgroundHandler as dispatcher because it has a looper (unlike
+ // "backgroundDispatcher" which does not have a looper) and input receiver could use
+ // its background looper and choreographer
+ .flowOn(backgroundHandler.asCoroutineDispatcher())
+
+ override val cursorPositions: Flow<CursorPosition> = createInputMonitorCallbackFlow(displayId)
+
+ override fun destroy() {
+ scope?.close()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ /**
+ * Creates a new instance of [SingleDisplayCursorPositionRepositoryImpl] for a given
+ * [displayId].
+ */
+ fun create(
+ displayId: Int,
+ listenerBuilder: InputEventListenerBuilder = defaultInputEventListenerBuilder,
+ inputMonitorBuilder: InputMonitorBuilder = defaultInputMonitorBuilder,
+ ): SingleDisplayCursorPositionRepositoryImpl
+ }
+
+ companion object {
+ private const val TAG = "CursorPositionPerDisplayRepositoryImpl"
+
+ private val defaultInputMonitorBuilder = InputMonitorBuilder { name, displayId ->
+ InputMonitorCompat(name, displayId)
+ }
+
+ val defaultInputEventListenerBuilder = InputEventListenerBuilder { channel ->
+ InputChannelCompat.InputEventListener { event ->
+ if (
+ event is MotionEvent &&
+ (event.source == SOURCE_MOUSE || event.source == SOURCE_TOUCHPAD)
+ ) {
+ val cursorEvent = CursorPosition(event.x, event.y, event.displayId)
+ channel.trySendWithFailureLogging(cursorEvent, TAG)
+ }
+ }
+ }
+ }
+}
+
+fun interface InputEventListenerBuilder {
+ fun build(channel: SendChannel<CursorPosition>): InputChannelCompat.InputEventListener
+}
+
+fun interface InputMonitorBuilder {
+ fun build(name: String, displayId: Int): InputMonitorCompat
+}
+
+@SysUISingleton
+class SingleDisplayCursorPositionRepositoryFactory
+@Inject
+constructor(private val factory: SingleDisplayCursorPositionRepositoryImpl.Factory) :
+ PerDisplayInstanceProviderWithTeardown<SingleDisplayCursorPositionRepository> {
+ override fun createInstance(displayId: Int): SingleDisplayCursorPositionRepository {
+ return factory.create(displayId)
+ }
+
+ override fun destroyInstance(instance: SingleDisplayCursorPositionRepository) {
+ instance.destroy()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepository.kt
new file mode 100644
index 000000000000..5a4ee16e0e64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/UserVisibleAppsRepository.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.systemui.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.os.Handler
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class UserVisibleAppsRepository
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ @Background private val bgExecutor: Executor,
+ @Background private val bgHandler: Handler,
+ private val launcherApps: LauncherApps,
+) {
+
+ val userVisibleApps: Flow<List<LauncherActivityInfo>>
+ get() = conflatedCallbackFlow {
+ val packageChangeCallback: LauncherApps.Callback =
+ object : LauncherApps.Callback() {
+ override fun onPackageAdded(packageName: String, userHandle: UserHandle) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_PACKAGE_ADDED,
+ )
+ }
+
+ override fun onPackageChanged(packageName: String, userHandle: UserHandle) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_PACKAGE_CHANGED,
+ )
+ }
+
+ override fun onPackageRemoved(packageName: String, userHandle: UserHandle) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_PACKAGE_REMOVED,
+ )
+ }
+
+ override fun onPackagesAvailable(
+ packages: Array<out String>,
+ userHandle: UserHandle,
+ replacing: Boolean,
+ ) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_PACKAGES_AVAILABLE,
+ )
+ }
+
+ override fun onPackagesUnavailable(
+ packages: Array<out String>,
+ userHandle: UserHandle,
+ replacing: Boolean,
+ ) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_PACKAGES_UNAVAILABLE,
+ )
+ }
+ }
+
+ val userChangeCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = ON_USER_CHANGED,
+ )
+ }
+ }
+
+ userTracker.addCallback(userChangeCallback, bgExecutor)
+ launcherApps.registerCallback(packageChangeCallback, bgHandler)
+
+ trySendWithFailureLogging(
+ element = retrieveLauncherApps(),
+ loggingTag = TAG,
+ elementDescription = INITIAL_VALUE,
+ )
+
+ awaitClose {
+ userTracker.removeCallback(userChangeCallback)
+ launcherApps.unregisterCallback(packageChangeCallback)
+ }
+ }
+
+ private fun retrieveLauncherApps(): List<LauncherActivityInfo> {
+ return launcherApps.getActivityList(/* packageName= */ null, userTracker.userHandle)
+ }
+
+ private companion object {
+ const val TAG = "UserVisibleAppsRepository"
+ const val ON_PACKAGE_ADDED = "onPackageAdded"
+ const val ON_PACKAGE_CHANGED = "onPackageChanged"
+ const val ON_PACKAGE_REMOVED = "onPackageRemoved"
+ const val ON_PACKAGES_AVAILABLE = "onPackagesAvailable"
+ const val ON_PACKAGES_UNAVAILABLE = "onPackagesUnavailable"
+ const val ON_USER_CHANGED = "onUserChanged"
+ const val INITIAL_VALUE = "InitialValue"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index fc79c7ff118d..50ebbe497651 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -32,7 +32,7 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.Intra
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@@ -45,7 +45,7 @@ class KeyguardBlueprintInteractor
constructor(
private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
@Application private val applicationScope: CoroutineScope,
- shadeInteractor: ShadeInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
@ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
private val fingerprintPropertyInteractor: FingerprintPropertyInteractor,
private val smartspaceSection: SmartspaceSection,
@@ -61,7 +61,7 @@ constructor(
/** Current BlueprintId */
val blueprintId =
- shadeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
+ shadeModeInteractor.isShadeLayoutWide.map { isShadeLayoutWide ->
val useSplitShade = isShadeLayoutWide && !SceneContainerFlag.isEnabled
when {
useSplitShade -> SplitShadeKeyguardBlueprint.ID
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index d749e3c11378..e758768aa5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -73,7 +73,7 @@ import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
@@ -113,7 +113,7 @@ constructor(
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val indicationController: KeyguardIndicationController,
@Assisted bundle: Bundle,
- private val shadeInteractor: ShadeInteractor,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val secureSettings: SecureSettings,
private val defaultShortcutsSection: DefaultShortcutsSection,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
@@ -415,10 +415,7 @@ constructor(
setUpClock(previewContext, rootView)
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
setUpSmartspace(previewContext, keyguardRootView)
- KeyguardPreviewSmartspaceViewBinder.bind(
- keyguardRootView,
- smartspaceViewModel,
- )
+ KeyguardPreviewSmartspaceViewBinder.bind(keyguardRootView, smartspaceViewModel)
}
KeyguardPreviewClockViewBinder.bind(
keyguardRootView,
@@ -591,7 +588,7 @@ constructor(
private fun getPreviewShadeLayoutWide(display: Display): Boolean {
return if (display.displayId == 0) {
- shadeInteractor.isShadeLayoutWide.value
+ shadeModeInteractor.isShadeLayoutWide.value
} else {
// For the unfolded preview in a folded screen; it's landscape by default
// For the folded preview in an unfolded screen; it's portrait by default
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index e268050234ab..f717431f6a40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -34,7 +34,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -56,7 +56,7 @@ constructor(
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val systemBarUtilsState: SystemBarUtilsState,
private val rootViewModel: KeyguardRootViewModel,
- private val shadeInteractor: ShadeInteractor,
+ private val shadeModeInteractor: ShadeModeInteractor,
) : KeyguardSection() {
private var nicBindingDisposable: DisposableHandle? = null
@@ -99,7 +99,7 @@ constructor(
context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
val height = context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
val isVisible = rootViewModel.isNotifIconContainerVisible.value
- val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
+ val isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value
constraintSet.apply {
if (PromotedNotificationUiAod.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index 2110c4027667..efdc5abf1f67 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -28,7 +28,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
@@ -40,7 +40,7 @@ class AodPromotedNotificationSection
constructor(
@ShadeDisplayAware private val context: Context,
private val viewModelFactory: AODPromotedNotificationViewModel.Factory,
- private val shadeInteractor: ShadeInteractor,
+ private val shadeModeInteractor: ShadeModeInteractor,
private val logger: PromotedNotificationLogger,
) : KeyguardSection() {
var view: ComposeView? = null
@@ -90,7 +90,7 @@ constructor(
context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start_icons)
constraintSet.apply {
- val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
+ val isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value
if (isShadeLayoutWide) {
// When in split shade, align with top of smart space:
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index f8425c16c341..5cc34e749b46 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -22,7 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -40,7 +40,7 @@ constructor(
smartspaceController: LockscreenSmartspaceController,
keyguardClockViewModel: KeyguardClockViewModel,
smartspaceInteractor: KeyguardSmartspaceInteractor,
- shadeInteractor: ShadeInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
) {
/** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled
@@ -91,7 +91,7 @@ constructor(
/* trigger clock and smartspace constraints change when smartspace appears */
val bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
- val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
+ val isShadeLayoutWide: StateFlow<Boolean> = shadeModeInteractor.isShadeLayoutWide
companion object {
fun getDateWeatherStartMargin(context: Context): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
index d6d185195c51..063ff15054e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
@@ -267,7 +267,11 @@ private fun CardCarouselContent(
}
if (behavior.isCarouselDismissible) {
- SwipeToDismiss(content = { PagerContent() }, onDismissed = onDismissed)
+ SwipeToDismiss(
+ content = { PagerContent() },
+ isSwipingEnabled = isSwipingEnabled,
+ onDismissed = onDismissed,
+ )
} else {
val overscrollEffect = rememberOffsetOverscrollEffect()
SwipeToReveal(
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToDismiss.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToDismiss.kt
index b80bf4143252..f044257bb343 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToDismiss.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToDismiss.kt
@@ -17,99 +17,187 @@
package com.android.systemui.media.remedia.ui.compose
import androidx.compose.animation.core.Animatable
-import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.absoluteOffset
+import androidx.compose.foundation.overscroll
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.fastRoundToInt
+import com.android.compose.gesture.NestedDraggable
+import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect
+import com.android.compose.gesture.nestedDraggable
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/** Swipe to dismiss that supports nested scrolling. */
@Composable
fun SwipeToDismiss(
- content: @Composable (overscrollEffect: OverscrollEffect?) -> Unit,
+ content: @Composable () -> Unit,
+ isSwipingEnabled: Boolean,
onDismissed: () -> Unit,
modifier: Modifier = Modifier,
) {
- val scope = rememberCoroutineScope()
- val offsetAnimatable = remember { Animatable(0f) }
+ val overscrollEffect = rememberOffsetOverscrollEffect()
- // This is the width of the revealed content UI box. It's not a state because it's not
- // observed in any composition and is an object with a value to avoid the extra cost
- // associated with boxing and unboxing an int.
- val revealedContentBoxWidth = remember {
+ // This is the width of the content UI box. It's not a state because it's not observed in any
+ // composition and is an object with a value to avoid the extra cost associated with boxing and
+ // unboxing an int.
+ val contentBoxWidth = remember {
object {
var value = 0
}
}
- val nestedScrollConnection = remember {
- object : NestedScrollConnection {
- override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
- return if (offsetAnimatable.value > 0f && available.x < 0f) {
- scope.launch { offsetAnimatable.snapTo(offsetAnimatable.value + available.x) }
- Offset(available.x, 0f)
- } else if (offsetAnimatable.value < 0f && available.x > 0f) {
- scope.launch { offsetAnimatable.snapTo(offsetAnimatable.value + available.x) }
- Offset(available.x, 0f)
- } else {
- Offset.Zero
- }
- }
-
- override fun onPostScroll(
- consumed: Offset,
- available: Offset,
- source: NestedScrollSource,
- ): Offset {
- return if (available.x > 0f) {
- scope.launch { offsetAnimatable.snapTo(offsetAnimatable.value + available.x) }
- Offset(available.x, 0f)
- } else if (available.x < 0f) {
- scope.launch { offsetAnimatable.snapTo(offsetAnimatable.value + available.x) }
- Offset(available.x, 0f)
- } else {
- Offset.Zero
- }
- }
+ // In order to support the drag to dismiss, infrastructure has to be put in place where a
+ // NestedDraggable helps by consuming the unconsumed drags and flings and applying the offset.
+ //
+ // This is the NestedDraggalbe controller.
+ val dragController =
+ rememberDismissibleContentDragController(
+ maxBound = { contentBoxWidth.value.toFloat() },
+ onDismissed = onDismissed,
+ )
- override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- scope.launch {
- offsetAnimatable.animateTo(
- if (offsetAnimatable.value >= revealedContentBoxWidth.value / 2f) {
- revealedContentBoxWidth.value * 2f
- } else if (offsetAnimatable.value <= -revealedContentBoxWidth.value / 2f) {
- -revealedContentBoxWidth.value * 2f
- } else {
- 0f
- }
- )
- if (offsetAnimatable.value != 0f) {
- onDismissed()
+ Box(
+ modifier =
+ modifier
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ contentBoxWidth.value = placeable.measuredWidth
+ layout(placeable.measuredWidth, placeable.measuredHeight) {
+ placeable.place(0, 0)
}
}
- return super.onPostFling(consumed, available)
- }
+ .nestedDraggable(
+ enabled = isSwipingEnabled,
+ draggable =
+ remember {
+ object : NestedDraggable {
+ override fun onDragStarted(
+ position: Offset,
+ sign: Float,
+ pointersDown: Int,
+ pointerType: PointerType?,
+ ): NestedDraggable.Controller {
+ return dragController
+ }
+
+ override fun shouldConsumeNestedPostScroll(sign: Float): Boolean {
+ return dragController.shouldConsumePostScrolls(sign)
+ }
+
+ override fun shouldConsumeNestedPreScroll(sign: Float): Boolean {
+ return dragController.shouldConsumePreScrolls(sign)
+ }
+ }
+ },
+ orientation = Orientation.Horizontal,
+ )
+ .overscroll(overscrollEffect)
+ .absoluteOffset { IntOffset(dragController.offset.fastRoundToInt(), y = 0) }
+ ) {
+ content()
+ }
+}
+
+@Composable
+private fun rememberDismissibleContentDragController(
+ maxBound: () -> Float,
+ onDismissed: () -> Unit,
+): DismissibleContentDragController {
+ val scope = rememberCoroutineScope()
+ return remember {
+ DismissibleContentDragController(
+ scope = scope,
+ maxBound = maxBound,
+ onDismissed = onDismissed,
+ )
+ }
+}
+
+private class DismissibleContentDragController(
+ private val scope: CoroutineScope,
+ private val maxBound: () -> Float,
+ private val onDismissed: () -> Unit,
+) : NestedDraggable.Controller {
+ private val offsetAnimatable = Animatable(0f)
+ private var lastTarget = 0f
+ private var range = 0f..1f
+ private var shouldConsumePreScrolls by mutableStateOf(false)
+
+ override val autoStopNestedDrags: Boolean
+ get() = true
+
+ val offset: Float
+ get() = offsetAnimatable.value
+
+ fun shouldConsumePreScrolls(sign: Float): Boolean {
+ if (!shouldConsumePreScrolls) return false
+
+ if (lastTarget > 0f && sign < 0f) {
+ range = 0f..maxBound()
+ return true
}
+
+ if (lastTarget < 0f && sign > 0f) {
+ range = -maxBound()..0f
+ return true
+ }
+
+ return false
}
- Box(
- modifier =
- modifier
- .onSizeChanged { revealedContentBoxWidth.value = it.width }
- .nestedScroll(nestedScrollConnection)
- .offset { IntOffset(x = offsetAnimatable.value.fastRoundToInt(), y = 0) }
- ) {
- content(null)
+ fun shouldConsumePostScrolls(sign: Float): Boolean {
+ val max = maxBound()
+ if (sign > 0f && lastTarget < max) {
+ range = 0f..maxBound()
+ return true
+ }
+
+ if (sign < 0f && lastTarget > -max) {
+ range = -maxBound()..0f
+ return true
+ }
+
+ return false
+ }
+
+ override fun onDrag(delta: Float): Float {
+ val previousTarget = lastTarget
+ lastTarget = (lastTarget + delta).fastCoerceIn(range.start, range.endInclusive)
+ val newTarget = lastTarget
+ scope.launch { offsetAnimatable.snapTo(newTarget) }
+ return lastTarget - previousTarget
+ }
+
+ override suspend fun onDragStopped(velocity: Float, awaitFling: suspend () -> Unit): Float {
+ val rangeMiddle = range.start + (range.endInclusive - range.start) / 2f
+ lastTarget =
+ when {
+ lastTarget >= rangeMiddle -> range.endInclusive
+ else -> range.start
+ }
+
+ shouldConsumePreScrolls = lastTarget != 0f
+ val newTarget = lastTarget
+
+ scope.launch {
+ offsetAnimatable.animateTo(newTarget)
+ if (newTarget != 0f) {
+ onDismissed()
+ }
+ }
+ return velocity
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToReveal.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToReveal.kt
index 770762c7a29f..1d7b79d9a07a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToReveal.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/SwipeToReveal.kt
@@ -22,7 +22,6 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.absoluteOffset
-import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.overscroll
import androidx.compose.foundation.withoutVisualEffect
import androidx.compose.runtime.Composable
@@ -82,7 +81,7 @@ fun SwipeToReveal(
// overscroll visual effect.
//
// This is the NestedDraggalbe controller.
- val revealedContentDragController = rememberRevealedContentDragController {
+ val revealedContentDragController = rememberDismissibleContentDragController {
revealedContentBoxWidth.value.toFloat()
}
@@ -186,7 +185,7 @@ fun SwipeToReveal(
}
@Composable
-private fun rememberRevealedContentDragController(
+private fun rememberDismissibleContentDragController(
maxBound: () -> Float
): RevealedContentDragController {
val scope = rememberCoroutineScope()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index ad0acbdaf702..8bc3203ea51e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -502,10 +502,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
boolean mHasBlurs;
@Override
- public void onWallpaperZoomOutChanged(float zoomOut) {
- }
-
- @Override
public void onBlurRadiusChanged(int radius) {
boolean hasBlurs = radius != 0;
if (hasBlurs == mHasBlurs) {
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 18b4b7d2b5cf..465c78e91e53 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -50,6 +51,7 @@ constructor(
val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
+ shadeModeInteractor: ShadeModeInteractor,
disableFlagsInteractor: DisableFlagsInteractor,
mediaCarouselInteractor: MediaCarouselInteractor,
activeNotificationsInteractor: ActiveNotificationsInteractor,
@@ -62,13 +64,13 @@ constructor(
traceName = "showClock",
initialValue =
shouldShowClock(
- isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+ isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value,
areAnyNotificationsPresent =
activeNotificationsInteractor.areAnyNotificationsPresentValue,
),
source =
combine(
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
this::shouldShowClock,
),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
index bd7796118038..de1b180f5a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
@@ -47,7 +47,7 @@ constructor(
scope.launchTraced("ShadeStateTraceLogger") {
launch {
val stateLogger = createTraceStateLogger("isShadeLayoutWide")
- shadeInteractor.isShadeLayoutWide.collect { stateLogger.log(it.toString()) }
+ shadeModeInteractor.isShadeLayoutWide.collect { stateLogger.log(it.toString()) }
}
launch {
val stateLogger = createTraceStateLogger("shadeMode")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6d68796454eb..b54b518ffbd6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -56,15 +56,6 @@ interface ShadeInteractor : BaseShadeInteractor {
/** Whether the shade can be expanded from QQS to QS. */
val isExpandToQsEnabled: Flow<Boolean>
-
- /**
- * Whether the shade layout should be wide (true) or narrow (false).
- *
- * In a wide layout, notifications and quick settings each take up only half the screen width
- * (whether they are shown at the same time or not). In a narrow layout, they can each be as
- * wide as the entire screen.
- */
- val isShadeLayoutWide: StateFlow<Boolean>
}
/** ShadeInteractor methods with implementations that differ between non-empty impls. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 77e6a833c153..4154e2ca281b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -46,7 +46,6 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
- override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index e8b5d5bdf7df..fb3fc524536d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,11 +55,7 @@ constructor(
userSetupRepository: UserSetupRepository,
userSwitcherInteractor: UserSwitcherInteractor,
private val baseShadeInteractor: BaseShadeInteractor,
- shadeModeInteractor: ShadeModeInteractor,
-) :
- ShadeInteractor,
- BaseShadeInteractor by baseShadeInteractor,
- ShadeModeInteractor by shadeModeInteractor {
+) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
disableFlagsInteractor.disableFlags
.map { it.isShadeEnabled() }
@@ -127,8 +123,4 @@ constructor(
disableFlags.isQuickSettingsEnabled() &&
!isDozing
}
-
- companion object {
- private const val TAG = "ShadeInteractor"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index c264e3525026..d03f09175b04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -99,12 +99,12 @@ constructor(
traceName = "showClock",
initialValue =
shouldShowClock(
- isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+ isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value,
overlays = sceneInteractor.currentOverlays.value,
),
source =
combine(
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
sceneInteractor.currentOverlays,
::shouldShowClock,
),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index d2f424a46620..ba446837a72e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -74,7 +74,7 @@ constructor(
private val singleShadeOverScrollerFactory: SingleShadeLockScreenOverScroller.Factory,
private val activityStarter: ActivityStarter,
wakefulnessLifecycle: WakefulnessLifecycle,
- configurationController: ConfigurationController,
+ @ShadeDisplayAware configurationController: ConfigurationController,
falsingManager: FalsingManager,
dumpManager: DumpManager,
qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 2030606e4274..72ece3db307b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -73,7 +73,7 @@ public class NotificationGroupingUtil {
}
return null;
} else {
- return row.getEntry().getSbn().getNotification();
+ return row.getEntryLegacy().getSbn().getNotification();
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index e292bcf1f7a8..f844d1da1a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -342,7 +342,6 @@ constructor(
keyguardInteractor.setZoomOut(zoomOutFromShadeRadius)
}
listeners.forEach {
- it.onWallpaperZoomOutChanged(zoomOutFromShadeRadius)
it.onBlurRadiusChanged(appliedBlurRadius)
}
notificationShadeWindowController.setBackgroundBlurRadius(appliedBlurRadius)
@@ -761,9 +760,6 @@ constructor(
/** Invoked when changes are needed in z-space */
interface DepthListener {
- /** Current wallpaper zoom out, where 0 is the closest, and 1 the farthest */
- fun onWallpaperZoomOutChanged(zoomOut: Float)
-
fun onBlurRadiusChanged(blurRadius: Int) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index df8fb5e75368..afbec7f356b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -71,46 +71,49 @@ constructor(
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
else if (entry.ranking.isConversation)
Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
- else
- Notification.MessagingStyle.CONVERSATION_TYPE_LEGACY
+ else Notification.MessagingStyle.CONVERSATION_TYPE_LEGACY
entry.ranking.conversationShortcutInfo?.let { shortcutInfo ->
logger.logAsyncTaskProgress(entry.logKey, "getting shortcut icon")
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label }
}
- if (NmSummarizationUiFlag.isEnabled && !TextUtils.isEmpty(entry.ranking.summarization)) {
- val icon = context.getDrawable(R.drawable.ic_notification_summarization)?.mutate()
- val imageSpan =
- icon?.let {
- it.setBounds(
- /* left= */ 0,
- /* top= */ 0,
- icon.getIntrinsicWidth(),
- icon.getIntrinsicHeight(),
- )
- ImageSpan(it, ImageSpan.ALIGN_CENTER)
- }
- val decoratedSummary =
- SpannableString("x " + entry.ranking.summarization).apply {
- setSpan(
- /* what = */ imageSpan,
- /* start = */ 0,
- /* end = */ 1,
- /* flags = */ Spanned.SPAN_INCLUSIVE_EXCLUSIVE,
- )
- entry.ranking.summarization?.let {
+ if (NmSummarizationUiFlag.isEnabled) {
+ if (!TextUtils.isEmpty(entry.ranking.summarization)) {
+ val icon = context.getDrawable(R.drawable.ic_notification_summarization)?.mutate()
+ val imageSpan =
+ icon?.let {
+ it.setBounds(
+ /* left= */ 0,
+ /* top= */ 0,
+ icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(),
+ )
+ ImageSpan(it, ImageSpan.ALIGN_CENTER)
+ }
+ val decoratedSummary =
+ SpannableString("x " + entry.ranking.summarization).apply {
setSpan(
- /* what = */ StyleSpan(Typeface.ITALIC),
- /* start = */ 2,
- /* end = */ it.length + 2,
- /* flags = */ Spanned.SPAN_EXCLUSIVE_INCLUSIVE,
+ /* what = */ imageSpan,
+ /* start = */ 0,
+ /* end = */ 1,
+ /* flags = */ Spanned.SPAN_INCLUSIVE_EXCLUSIVE,
)
+ entry.ranking.summarization?.let {
+ setSpan(
+ /* what = */ StyleSpan(Typeface.ITALIC),
+ /* start = */ 2,
+ /* end = */ it.length + 2,
+ /* flags = */ Spanned.SPAN_EXCLUSIVE_INCLUSIVE,
+ )
+ }
}
- }
- entry.sbn.notification.extras.putCharSequence(
- EXTRA_SUMMARIZED_CONTENT,
- decoratedSummary,
- )
+ entry.sbn.notification.extras.putCharSequence(
+ EXTRA_SUMMARIZED_CONTENT,
+ decoratedSummary,
+ )
+ } else {
+ entry.sbn.notification.extras.putCharSequence(EXTRA_SUMMARIZED_CONTENT, null)
+ }
}
messagingStyle.unreadMessageCount =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index ba4001014681..e19ffb4f2018 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -17,4 +17,4 @@ valiiftime@google.com
yurilin@google.com
per-file MediaNotificationProcessor.java = ethibodeau@google.com
-per-file MagicActionBackgroundDrawable.kt = dupin@google.com
+per-file AnimatedActionBackgroundDrawable.kt = dupin@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
index 68c13afe82dc..7b0d90ca3b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
@@ -24,14 +24,19 @@ import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator.Companion.createDefaultSpring
import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import kotlin.math.sign
/**
* A physically animatable property of a view.
*
* @param tag the view tag to safe this property in
* @param property the property to animate.
+ * @param avoidDoubleOvershoot should this property avoid double overshoot when animated
*/
-data class PhysicsProperty(val tag: Int, val property: Property<View, Float>) {
+data class PhysicsProperty
+@JvmOverloads constructor(
+ val tag: Int, val property: Property<View, Float>, val avoidDoubleOvershoot: Boolean = true
+) {
val offsetProperty =
object : FloatProperty<View>(property.name) {
override fun get(view: View): Float {
@@ -61,6 +66,8 @@ data class PropertyData(
var offset: Float = 0f,
var animator: SpringAnimation? = null,
var delayRunnable: Runnable? = null,
+ var startOffset: Float = 0f,
+ var doubleOvershootAvoidingListener: DynamicAnimation.OnAnimationUpdateListener? = null
)
/**
@@ -140,30 +147,67 @@ private fun startAnimation(
if (animator == null) {
animator = SpringAnimation(view, animatableProperty.offsetProperty)
propertyData.animator = animator
- animator.setSpring(createDefaultSpring())
val listener = properties?.getAnimationEndListener(animatableProperty.property)
if (listener != null) {
animator.addEndListener(listener)
- // We always notify things as started even if we have a delay
- properties.getAnimationStartListener(animatableProperty.property)?.accept(animator)
}
+ // We always notify things as started even if we have a delay
+ properties?.getAnimationStartListener(animatableProperty.property)?.accept(animator)
// remove the tag when the animation is finished
- animator.addEndListener { _, _, _, _ -> propertyData.animator = null }
+ animator.addEndListener { _, _, _, _ ->
+ propertyData.animator = null
+ propertyData.doubleOvershootAvoidingListener = null
+ // Let's make sure we never get stuck with an offset even when canceling
+ // We never actually cancel running animations but keep it around, so this only
+ // triggers if things really should end.
+ propertyData.offset = 0f
+ }
+ }
+ if (animatableProperty.avoidDoubleOvershoot
+ && propertyData.doubleOvershootAvoidingListener == null) {
+ propertyData.doubleOvershootAvoidingListener =
+ DynamicAnimation.OnAnimationUpdateListener { _, offset: Float, velocity: Float ->
+ val isOscillatingBackwards = velocity.sign == propertyData.startOffset.sign
+ val didAlreadyRemoveBounciness =
+ animator.spring.dampingRatio == SpringForce.DAMPING_RATIO_NO_BOUNCY
+ val isOvershooting = offset.sign != propertyData.startOffset.sign
+ if (isOvershooting && isOscillatingBackwards && !didAlreadyRemoveBounciness) {
+ // our offset is starting to decrease, let's remove all overshoot
+ animator.spring.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ } else if (!isOvershooting
+ && (didAlreadyRemoveBounciness || isOscillatingBackwards)) {
+ // we already did overshoot, let's skip to the end to avoid oscillations.
+ // Usually we shouldn't hit this as setting the damping ratio avoid overshoots
+ // but it may still happen if we see jank
+ animator.skipToEnd();
+ }
+ }
+ animator.addUpdateListener(propertyData.doubleOvershootAvoidingListener)
+ } else if (!animatableProperty.avoidDoubleOvershoot
+ && propertyData.doubleOvershootAvoidingListener != null) {
+ animator.removeUpdateListener(propertyData.doubleOvershootAvoidingListener)
}
+ // reset a new spring as it may have been modified
+ animator.setSpring(createDefaultSpring().setFinalPosition(0f))
// TODO(b/393581344): look at custom spring
endListener?.let { animator.addEndListener(it) }
- val newOffset = previousEndValue - newEndValue + propertyData.offset
- // Immedialely set the new offset that compensates for the immediate end value change
- propertyData.offset = newOffset
- property.set(view, newEndValue + newOffset)
+ val startOffset = previousEndValue - newEndValue + propertyData.offset
+ // Immediately set the new offset that compensates for the immediate end value change
+ propertyData.offset = startOffset
+ propertyData.startOffset = startOffset
+ property.set(view, newEndValue + startOffset)
// cancel previous starters still pending
view.removeCallbacks(propertyData.delayRunnable)
- animator.setStartValue(newOffset)
+ animator.setStartValue(startOffset)
val startRunnable = Runnable {
animator.animateToFinalPosition(0f)
propertyData.delayRunnable = null
+ // When setting a new spring on a running animation it doesn't properly set the finish
+ // conditions and will never actually end them only calling start explicitly does that,
+ // so let's start them again!
+ animator.start()
}
if (properties != null && properties.delay > 0 && !animator.isRunning) {
propertyData.delayRunnable = startRunnable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 87733725e133..a40a2285d8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
import android.util.ArrayMap
import android.util.ArraySet
import com.android.internal.annotations.VisibleForTesting
@@ -48,7 +49,10 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl.DecisionImpl
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.row.NotificationActionClickManager
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix
@@ -81,6 +85,7 @@ class HeadsUpCoordinator
constructor(
@Application private val applicationScope: CoroutineScope,
private val mLogger: HeadsUpCoordinatorLogger,
+ private val mInterruptLogger: VisualInterruptionDecisionLogger,
private val mSystemClock: SystemClock,
private val notifCollection: NotifCollection,
private val mHeadsUpManager: HeadsUpManager,
@@ -290,6 +295,19 @@ constructor(
return@forEach
}
+ if (isDisqualifiedChild(childToReceiveParentHeadsUp)) {
+ mInterruptLogger.logDecision(
+ VisualInterruptionType.PEEK.name,
+ childToReceiveParentHeadsUp,
+ DecisionImpl(shouldInterrupt = false,
+ logReason = "disqualified-transfer-target"))
+ postedEntries.forEach {
+ it.shouldHeadsUpEver = false
+ it.shouldHeadsUpAgain = false
+ handlePostedEntry(it, hunMutator, scenario = "disqualified-transfer-target")
+ }
+ return@forEach
+ }
// At this point we just need to initiate the transfer
val summaryUpdate = mPostedEntries[logicalSummary.key]
@@ -392,6 +410,14 @@ constructor(
cleanUpEntryTimes()
}
+ private fun isDisqualifiedChild(entry: NotificationEntry): Boolean {
+ if (entry.channel == null || entry.channel.id == null) {
+ return false
+ }
+ return entry.channel.id in SYSTEM_RESERVED_IDS
+ }
+
+
/**
* Find the posted child with the newest when, and return it if it is isolated and has
* GROUP_ALERT_SUMMARY so that it can be heads uped.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 8240a0abaa9d..9f67e50ef920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -87,7 +87,7 @@ constructor(
val eventLogData: EventLogData?
}
- private class DecisionImpl(
+ class DecisionImpl(
override val shouldInterrupt: Boolean,
override val logReason: String,
) : Decision
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 175512336b8e..bd06a375d5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -33,6 +33,7 @@ import android.service.notification.StatusBarNotification;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
@@ -64,6 +65,8 @@ public interface NotificationPanelLogger {
*/
void logNotificationDrag(NotificationEntry draggedNotification);
+ void logNotificationDrag(EntryAdapter draggedNotification);
+
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "Notification panel shown from status bar.")
NOTIFICATION_PANEL_OPEN_STATUS_BAR(200),
@@ -123,6 +126,42 @@ public interface NotificationPanelLogger {
}
/**
+ * Composes a NotificationsList proto from the list of visible notifications.
+ * @param visibleNotifications as provided by NotificationEntryManager.getVisibleNotifications()
+ * @return NotificationList proto suitable for SysUiStatsLog.write(NOTIFICATION_PANEL_REPORTED)
+ */
+ static Notifications.NotificationList adapterToNotificationProto(
+ @Nullable List<EntryAdapter> visibleNotifications) {
+ Notifications.NotificationList notificationList = new Notifications.NotificationList();
+ if (visibleNotifications == null) {
+ return notificationList;
+ }
+ final Notifications.Notification[] proto_array =
+ new Notifications.Notification[visibleNotifications.size()];
+ int i = 0;
+ for (EntryAdapter ne : visibleNotifications) {
+ final StatusBarNotification n = ne.getSbn();
+ if (n != null) {
+ final Notifications.Notification proto = new Notifications.Notification();
+ proto.uid = n.getUid();
+ proto.packageName = n.getPackageName();
+ if (n.getInstanceId() != null) {
+ proto.instanceId = n.getInstanceId().getId();
+ }
+ // TODO set np.groupInstanceId
+ if (n.getNotification() != null) {
+ proto.isGroupSummary = n.getNotification().isGroupSummary();
+ }
+ proto.section = toNotificationSection(ne.getSectionBucket());
+ proto_array[i] = proto;
+ }
+ ++i;
+ }
+ notificationList.notifications = proto_array;
+ return notificationList;
+ }
+
+ /**
* Maps PriorityBucket enum to Notification.SECTION constant. The two lists should generally
* use matching names, but the values may differ, because PriorityBucket order changes from
* time to time, while logs need to have stable meanings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index d7f7b760dd04..7eb74a4bf83d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.logging;
import static com.android.systemui.statusbar.notification.logging.NotificationPanelLogger.NotificationPanelEvent.NOTIFICATION_DRAG;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -62,4 +63,15 @@ public class NotificationPanelLoggerImpl implements NotificationPanelLogger {
/* num_notifications = */ proto.notifications.length,
/* notifications = */ MessageNano.toByteArray(proto));
}
+
+ @Override
+ public void logNotificationDrag(EntryAdapter draggedNotification) {
+ final Notifications.NotificationList proto =
+ NotificationPanelLogger.adapterToNotificationProto(
+ Collections.singletonList(draggedNotification));
+ SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+ /* event_id = */ NOTIFICATION_DRAG.getId(),
+ /* num_notifications = */ proto.notifications.length,
+ /* notifications = */ MessageNano.toByteArray(proto));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index fdbd75bd33ca..9282e166f605 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -16,13 +16,13 @@
package com.android.systemui.statusbar.notification.promoted
-import android.app.Flags
import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.content.Context
import android.graphics.PorterDuff
import android.util.Log
import android.view.LayoutInflater
+import android.view.NotificationTopLineView
import android.view.View
import android.view.View.GONE
import android.view.View.MeasureSpec.AT_MOST
@@ -202,7 +202,7 @@ private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : Frame
private val PromotedNotificationContentModel.layoutResource: Int?
get() {
- return if (Flags.notificationsRedesignTemplates()) {
+ return if (notificationsRedesignTemplates()) {
when (style) {
Style.Base -> R.layout.notification_2025_template_expanded_base
Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture
@@ -258,6 +258,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private val time: DateTimeView? = root.findViewById(R.id.time)
private val timeDivider: TextView? = root.findViewById(R.id.time_divider)
private val title: TextView? = root.findViewById(R.id.title)
+ private val topLine: NotificationTopLineView? = root.findViewById(R.id.notification_top_line)
private val verificationDivider: TextView? = root.findViewById(R.id.verification_divider)
private val verificationIcon: ImageView? = root.findViewById(R.id.verification_icon)
private val verificationText: TextView? = root.findViewById(R.id.verification_text)
@@ -266,6 +267,29 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private var oldProgressBar: ProgressBar? = null
private val newProgressBar = root.findViewById<View>(R.id.progress) as? NotificationProgressBar
+ private val largeIconSizePx: Int =
+ root.context.resources.getDimensionPixelSize(R.dimen.notification_right_icon_size)
+
+ private val endMarginPx: Int =
+ if (notificationsRedesignTemplates()) {
+ root.context.resources.getDimensionPixelSize(R.dimen.notification_2025_margin)
+ } else {
+ root.context.resources.getDimensionPixelSize(
+ systemuiR.dimen.notification_shade_content_margin_horizontal
+ )
+ }
+
+ private val imageEndMarginPx: Int
+ get() = largeIconSizePx + 2 * endMarginPx
+
+ private val PromotedNotificationContentModel.imageEndMarginPxIfHasLargeIcon: Int
+ get() =
+ if (!skeletonLargeIcon.isNullOrEmpty()) {
+ imageEndMarginPx
+ } else {
+ 0
+ }
+
init {
// Hide views that are never visible in the skeleton promoted notification.
alternateExpandTarget?.visibility = GONE
@@ -283,13 +307,20 @@ private class AODPromotedNotificationViewUpdater(root: View) {
?.mutate()
?.setColorFilter(SecondaryText.colorInt, PorterDuff.Mode.SRC_IN)
+ (rightIcon?.layoutParams as? MarginLayoutParams)?.let {
+ it.marginEnd = endMarginPx
+ rightIcon.layoutParams = it
+ }
+ bigText?.setImageEndMargin(largeIconSizePx + endMarginPx)
+ text?.setImageEndMargin(largeIconSizePx + endMarginPx)
+
setTextViewColor(appNameDivider, SecondaryText)
setTextViewColor(headerTextDivider, SecondaryText)
setTextViewColor(headerTextSecondaryDivider, SecondaryText)
setTextViewColor(timeDivider, SecondaryText)
setTextViewColor(verificationDivider, SecondaryText)
- if (Flags.notificationsRedesignTemplates()) {
+ if (notificationsRedesignTemplates()) {
(mainColumn?.layoutParams as? MarginLayoutParams)?.let { mainColumnMargins ->
mainColumnMargins.topMargin =
Notification.Builder.getContentMarginTop(
@@ -315,19 +346,15 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private fun updateBase(
content: PromotedNotificationContentModel,
- textView: ImageFloatingTextView? = null,
- showOldProgress: Boolean = true,
+ textView: ImageFloatingTextView? = text,
) {
- updateHeader(content, hideTitle = true)
+ updateHeader(content)
updateTitle(title, content)
- updateText(textView ?: text, content)
+ updateText(textView, content)
updateSmallIcon(icon, content)
updateImageView(rightIcon, content.skeletonLargeIcon)
-
- if (showOldProgress) {
- updateOldProgressBar(content)
- }
+ updateOldProgressBar(content)
}
private fun updateBigPictureStyle(content: PromotedNotificationContentModel) {
@@ -345,14 +372,15 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
private fun updateProgressStyle(content: PromotedNotificationContentModel) {
- updateBase(content, showOldProgress = false)
+ updateBase(content)
updateNewProgressBar(content)
}
private fun updateOldProgressBar(content: PromotedNotificationContentModel) {
if (
- content.oldProgress == null ||
+ content.style == Style.Progress ||
+ content.oldProgress == null ||
content.oldProgress.max == 0 ||
content.oldProgress.isIndeterminate
) {
@@ -381,27 +409,24 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
- private fun updateHeader(
- content: PromotedNotificationContentModel,
- hideTitle: Boolean = false,
- ) {
+ private fun updateHeader(content: PromotedNotificationContentModel) {
updateAppName(content)
updateTextView(headerTextSecondary, content.subText)
- if (!hideTitle) {
- updateTitle(headerText, content)
- }
+ // Not calling updateTitle(headerText, content) because the title is always a separate
+ // element in the expanded layout used for AOD RONs.
updateTimeAndChronometer(content)
- updateHeaderDividers(content, hideTitle = hideTitle)
+ updateHeaderDividers(content)
+
+ updateTopLine(content)
}
- private fun updateHeaderDividers(
- content: PromotedNotificationContentModel,
- hideTitle: Boolean = false,
- ) {
- val hasAppName = content.appName != null && content.appName.isNotEmpty()
- val hasSubText = content.subText != null && content.subText.isNotEmpty()
- val hasHeader = content.title != null && content.title.isNotEmpty() && !hideTitle
+ private fun updateHeaderDividers(content: PromotedNotificationContentModel) {
+ val hasAppName = content.appName != null
+ val hasSubText = content.subText != null
+ // Not setting hasHeader = content.title because the title is always a separate element in
+ // the expanded layout used for AOD RONs.
+ val hasHeader = false
val hasTimeOrChronometer = content.time != null
val hasTextBeforeSubText = hasAppName
@@ -418,26 +443,27 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
private fun updateConversationHeader(content: PromotedNotificationContentModel) {
- updateTitle(conversationText, content)
updateAppName(content)
updateTimeAndChronometer(content)
- updateConversationHeaderDividers(content, hideTitle = true)
-
updateImageView(verificationIcon, content.verificationIcon)
updateTextView(verificationText, content.verificationText)
+ updateConversationHeaderDividers(content)
+
+ updateTopLine(content)
+
updateSmallIcon(conversationIcon, content)
+ updateTitle(conversationText, content)
}
- private fun updateConversationHeaderDividers(
- content: PromotedNotificationContentModel,
- hideTitle: Boolean = false,
- ) {
- val hasTitle = content.title != null && !hideTitle
+ private fun updateConversationHeaderDividers(content: PromotedNotificationContentModel) {
+ // Not setting hasTitle = content.title because the title is always a separate element in
+ // the expanded layout used for AOD RONs.
+ val hasTitle = false
val hasAppName = content.appName != null
val hasTimeOrChronometer = content.time != null
val hasVerification =
- !content.verificationIcon.isNullOrEmpty() || !content.verificationText.isNullOrEmpty()
+ !content.verificationIcon.isNullOrEmpty() || content.verificationText != null
val hasTextBeforeAppName = hasTitle
val hasTextBeforeTime = hasTitle || hasAppName
@@ -457,6 +483,10 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) {
+ (titleView?.layoutParams as? MarginLayoutParams)?.let {
+ it.marginEnd = content.imageEndMarginPxIfHasLargeIcon
+ titleView.layoutParams = it
+ }
updateTextView(titleView, content.title, color = PrimaryText)
}
@@ -505,6 +535,10 @@ private class AODPromotedNotificationViewUpdater(root: View) {
chronometer?.appendFontFeatureSetting("tnum")
}
+ private fun updateTopLine(content: PromotedNotificationContentModel) {
+ topLine?.headerTextMarginEnd = content.imageEndMarginPxIfHasLargeIcon
+ }
+
private fun inflateOldProgressBar() {
if (oldProgressBar != null) {
return
@@ -518,7 +552,8 @@ private class AODPromotedNotificationViewUpdater(root: View) {
view: ImageFloatingTextView?,
content: PromotedNotificationContentModel,
) {
- view?.setHasImage(false)
+ view?.setHasImage(!content.skeletonLargeIcon.isNullOrEmpty())
+ view?.setNumIndentLines(if (content.title != null) 0 else 1)
updateTextView(view, content.text)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index a8a7e885d1f7..d9bdfbc81145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -183,9 +183,10 @@ constructor(
private fun Notification.smallIconModel(imageModelProvider: ImageModelProvider): ImageModel? =
imageModelProvider.getImageModel(smallIcon, SmallSquare)
- private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE)
+ private fun Notification.title(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_TITLE)
- private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG)
+ private fun Notification.bigTitle(): CharSequence? =
+ getCharSequenceExtraUnlessEmpty(EXTRA_TITLE_BIG)
private fun Notification.callPerson(): Person? =
extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java)
@@ -200,9 +201,10 @@ constructor(
} ?: title()
}
- private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT)
+ private fun Notification.text(): CharSequence? = getCharSequenceExtraUnlessEmpty(EXTRA_TEXT)
- private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT)
+ private fun Notification.bigText(): CharSequence? =
+ getCharSequenceExtraUnlessEmpty(EXTRA_BIG_TEXT)
private fun Notification.text(style: Notification.Style?): CharSequence? {
return when (style) {
@@ -211,17 +213,17 @@ constructor(
} ?: text()
}
- private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
+ private fun Notification.subText(): String? = getStringExtraUnlessEmpty(EXTRA_SUB_TEXT)
private fun Notification.shortCriticalText(): String? {
if (!android.app.Flags.apiRichOngoing()) {
return null
}
- if (this.shortCriticalText != null) {
- return this.shortCriticalText
+ if (shortCriticalText != null) {
+ return shortCriticalText
}
if (Flags.promoteNotificationsAutomatically()) {
- return this.extras?.getString(EXTRA_AUTOMATICALLY_EXTRACTED_SHORT_CRITICAL_TEXT)
+ return getStringExtraUnlessEmpty(EXTRA_AUTOMATICALLY_EXTRACTED_SHORT_CRITICAL_TEXT)
}
return null
}
@@ -277,7 +279,7 @@ constructor(
}
private fun Notification.verificationText(): CharSequence? =
- extras.getCharSequence(EXTRA_VERIFICATION_TEXT)
+ getCharSequenceExtraUnlessEmpty(EXTRA_VERIFICATION_TEXT)
private fun Notification.Builder.extractStyleContent(
notification: Notification,
@@ -344,3 +346,11 @@ constructor(
contentBuilder.newProgress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt())
}
}
+
+private fun Notification.getCharSequenceExtraUnlessEmpty(key: String): CharSequence? =
+ extras?.getCharSequence(key)?.takeUnlessEmpty()
+
+private fun Notification.getStringExtraUnlessEmpty(key: String): String? =
+ extras?.getString(key)?.takeUnlessEmpty()
+
+private fun <T : CharSequence> T.takeUnlessEmpty(): T? = takeUnless { it.isEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionBackgroundDrawable.kt
index a9ca6359b570..514e16557b65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionBackgroundDrawable.kt
@@ -36,7 +36,7 @@ import com.android.wm.shell.shared.animation.Interpolators
import android.graphics.drawable.RippleDrawable
import androidx.core.content.ContextCompat
-class MagicActionBackgroundDrawable(
+class AnimatedActionBackgroundDrawable(
context: Context,
) : RippleDrawable(
ContextCompat.getColorStateList(
@@ -56,13 +56,13 @@ class BaseBackgroundDrawable(
context: Context,
) : Drawable() {
- private val cornerRadius = context.resources.getDimension(R.dimen.magic_action_button_corner_radius)
- private val outlineStrokeWidth = context.resources.getDimension(R.dimen.magic_action_button_outline_stroke_width)
+ private val cornerRadius = context.resources.getDimension(R.dimen.animated_action_button_corner_radius)
+ private val outlineStrokeWidth = context.resources.getDimension(R.dimen.animated_action_button_outline_stroke_width)
private val insetVertical = 8 * context.resources.displayMetrics.density
private val buttonShape = Path()
// Color and style
- private val outlineStaticColor = context.getColor(R.color.magic_action_button_stroke_color)
+ private val outlineStaticColor = context.getColor(R.color.animated_action_button_stroke_color)
private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val bgColor =
context.getColor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionButton.kt
index d735360f1d4e..a1d75c69bf5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionButton.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AnimatedActionButton.kt
@@ -22,15 +22,15 @@ import android.util.AttributeSet
import android.widget.Button
/**
- * Custom Button for Magic Action Button, which includes the custom background and foreground.
+ * Custom Button for Animated Action Button, which includes the custom background and foreground.
*/
@SuppressLint("AppCompatCustomView")
-class MagicActionButton @JvmOverloads constructor(
+class AnimatedActionButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : Button(context, attrs, defStyleAttr) {
init {
- background = MagicActionBackgroundDrawable(context)
+ background = AnimatedActionBackgroundDrawable(context)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 20b826a3ca92..c04613af452a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -145,7 +145,11 @@ public class ExpandableNotificationRowDragController {
| View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
if (result) {
// Log notification drag only if it succeeds
- mNotificationPanelLogger.logNotificationDrag(enr.getEntry());
+ if (NotificationBundleUi.isEnabled()) {
+ mNotificationPanelLogger.logNotificationDrag(enr.getEntryAdapter());
+ } else {
+ mNotificationPanelLogger.logNotificationDrag(enr.getEntryLegacy());
+ }
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (enr.isPinned()) {
mHeadsUpManager.releaseAllImmediately();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 8cf9dd365b60..d1e6c6f335c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -30,6 +30,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import com.android.app.animation.Interpolators;
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.PhysicsProperty;
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator;
@@ -199,15 +200,19 @@ public class ExpandableViewState extends ViewState {
if (animateHeight) {
expandableView.setActualHeightAnimating(true);
}
+ DynamicAnimation.OnAnimationEndListener endListener = null;
+ if (!ViewState.isAnimating(expandableView, HEIGHT_PROPERTY)) {
+ // only Add the end listener if we haven't already
+ endListener = (animation, canceled, value, velocity) -> {
+ expandableView.setActualHeightAnimating(false);
+ if (!canceled && child instanceof ExpandableNotificationRow row) {
+ row.setGroupExpansionChanging(false /* isExpansionChanging */);
+ }
+ };
+ }
PhysicsPropertyAnimator.setProperty(child, HEIGHT_PROPERTY, this.height, properties,
animateHeight,
- (animation, canceled, value, velocity) -> {
- expandableView.setActualHeightAnimating(false);
- if (!canceled && child instanceof ExpandableNotificationRow) {
- ((ExpandableNotificationRow) child).setGroupExpansionChanging(
- false /* isExpansionChanging */);
- }
- });
+ endListener);
} else {
startHeightAnimationInterpolator(expandableView, properties);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5414318b29bd..f0c03016a604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -405,7 +405,7 @@ public class StackStateAnimator {
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
float velocity) {
mAnimatorSet.remove(animation);
- if (mAnimatorSet.isEmpty() && !canceled) {
+ if (mAnimatorSet.isEmpty()) {
onAnimationFinished();
}
mAnimationEndPool.push(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index 29dbeb2c8ee4..2dc5a8127412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -398,6 +398,14 @@ public class ViewState implements Dumpable {
return childTag != null;
}
+ public static boolean isAnimating(View view, PhysicsProperty property) {
+ Object childTag = getChildTag(view, property.getTag());
+ if (childTag instanceof PropertyData propertyData) {
+ return propertyData.getAnimator() != null;
+ }
+ return childTag != null;
+ }
+
/**
* Start an animation to this viewstate
*
@@ -680,13 +688,18 @@ public class ViewState implements Dumpable {
private void startYTranslationAnimation(final View child, AnimationProperties properties) {
if (mUsePhysicsForMovement) {
// Y Translation does some extra calls when it ends, so lets add a listener
- DynamicAnimation.OnAnimationEndListener endListener =
- (animation, canceled, value, velocity) -> {
- if (!canceled) {
- HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(child, false);
- onYTranslationAnimationFinished(child);
- }
- };
+ DynamicAnimation.OnAnimationEndListener endListener = null;
+ if (!isAnimatingY(child)) {
+ // Only add a listener if we're not animating yet
+ endListener =
+ (animation, canceled, value, velocity) -> {
+ if (!canceled) {
+ HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(child,
+ false);
+ onYTranslationAnimationFinished(child);
+ }
+ };
+ }
PhysicsPropertyAnimator.setProperty(child, Y_TRANSLATION, this.mYTranslation,
properties, properties.getAnimationFilter().animateY, endListener);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 9d55e1d9d592..0ea9509f0c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -245,7 +245,7 @@ constructor(
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.isShadeLayoutWide,
+ shadeModeInteractor.isShadeLayoutWide,
shadeModeInteractor.shadeMode,
configurationInteractor.onAnyConfigurationChange,
) { isShadeLayoutWide, shadeMode, _ ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 6d959be1c5f4..b33c2005479e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -397,13 +397,13 @@ constructor(
delayOnClickListener: Boolean,
packageContext: Context,
): Button {
- val isMagicAction =
- Flags.notificationMagicActionsTreatment() &&
+ val isAnimatedAction =
+ Flags.notificationAnimatedActionsTreatment() &&
smartActions.fromAssistant &&
- action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
+ action.extras.getBoolean(Notification.Action.EXTRA_IS_ANIMATED, false)
val layoutRes =
- if (isMagicAction) {
- R.layout.magic_action_button
+ if (isAnimatedAction) {
+ R.layout.animated_action_button
} else {
if (notificationsRedesignTemplates()) {
R.layout.notification_2025_smart_action_button
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index bd3feadf4459..f36f765bb78b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -537,8 +537,10 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
try {
JSONObject object = new JSONObject(overlayPackageJson);
String seedColorStr = Integer.toHexString(defaultSettings.seedColor.toArgb());
- object.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColorStr);
- object.put(OVERLAY_CATEGORY_ACCENT_COLOR, seedColorStr);
+ if(defaultSettings.colorSource == COLOR_SOURCE_PRESET){
+ object.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, seedColorStr);
+ object.put(OVERLAY_CATEGORY_ACCENT_COLOR, seedColorStr);
+ }
object.put(OVERLAY_COLOR_SOURCE, defaultSettings.colorSource);
object.put(OVERLAY_CATEGORY_THEME_STYLE, Style.toString(mThemeStyle));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/compose/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/compose/slider/Slider.kt
index 54d2f79509c3..0b11da362a82 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/compose/slider/Slider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/compose/slider/Slider.kt
@@ -18,9 +18,9 @@
package com.android.systemui.volume.ui.compose.slider
-import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -32,11 +32,11 @@ import androidx.compose.material3.SliderState
import androidx.compose.material3.VerticalSlider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
@@ -53,14 +53,9 @@ import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import kotlin.math.round
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-private val defaultSpring =
- SpringSpec<Float>(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessHigh)
@Composable
fun Slider(
@@ -87,55 +82,31 @@ fun Slider(
},
) {
require(stepDistance >= 0) { "stepDistance must not be negative" }
- val coroutineScope = rememberCoroutineScope()
- val snappedValue = snapValue(value, valueRange, stepDistance)
+ val snappedValue by valueState(value, isEnabled)
val hapticsViewModel = haptics.createViewModel(snappedValue, valueRange, interactionSource)
- val animatable = remember { Animatable(snappedValue) }
- var animationJob: Job? by remember { mutableStateOf(null) }
val sliderState =
remember(valueRange) { SliderState(value = snappedValue, valueRange = valueRange) }
val valueChange: (Float) -> Unit = { newValue ->
hapticsViewModel?.onValueChange(newValue)
- val snappedNewValue = snapValue(newValue, valueRange, stepDistance)
- if (animatable.targetValue != snappedNewValue) {
- onValueChanged(snappedNewValue)
- animationJob?.cancel()
- animationJob =
- coroutineScope.launch {
- animatable.animateTo(
- targetValue = snappedNewValue,
- animationSpec = defaultSpring,
- )
- }
- }
+ onValueChanged(newValue)
}
val semantics =
createSemantics(
accessibilityParams,
- animatable.targetValue,
+ snappedValue,
valueRange,
valueChange,
isEnabled,
stepDistance,
)
- LaunchedEffect(snappedValue) {
- if (!animatable.isRunning && animatable.targetValue != snappedValue) {
- animationJob?.cancel()
- animationJob =
- coroutineScope.launch {
- animatable.animateTo(targetValue = snappedValue, animationSpec = defaultSpring)
- }
- }
- }
-
sliderState.onValueChangeFinished = {
hapticsViewModel?.onValueChangeEnded()
- onValueChangeFinished?.invoke(animatable.targetValue)
+ onValueChangeFinished?.invoke(snappedValue)
}
sliderState.onValueChange = valueChange
- sliderState.value = animatable.value
+ sliderState.value = snappedValue
if (isVertical) {
VerticalSlider(
@@ -161,16 +132,26 @@ fun Slider(
}
}
-private fun snapValue(
- value: Float,
- valueRange: ClosedFloatingPointRange<Float>,
- stepDistance: Float,
-): Float {
- if (stepDistance == 0f) {
- return value
- }
- val coercedValue = value.coerceIn(valueRange)
- return Math.round(coercedValue / stepDistance) * stepDistance
+@Composable
+private fun valueState(targetValue: Float, isEnabled: Boolean): State<Float> {
+ var prevValue by remember { mutableFloatStateOf(targetValue) }
+ var prevEnabled by remember { mutableStateOf(isEnabled) }
+ // Don't animate slider value when receive the first value and when changing isEnabled state
+ val value =
+ if (prevEnabled != isEnabled) mutableFloatStateOf(targetValue)
+ else
+ animateFloatAsState(
+ targetValue = targetValue,
+ animationSpec =
+ spring(
+ dampingRatio = Spring.DampingRatioNoBouncy,
+ stiffness = Spring.StiffnessMedium,
+ ),
+ label = "VolumeSliderValueAnimation",
+ )
+ prevValue = targetValue
+ prevEnabled = isEnabled
+ return value
}
private fun createSemantics(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 061f7984d44b..2898a02a1da8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -618,6 +618,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */,
Display.DEFAULT_DISPLAY);
+ // Clear the dismiss override so we don't have behavior after dismissing the dialog
+ mGlobalActionsDialogLite.mDialog.setDismissOverride(null);
// Then smart lock will be disabled
verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser));
@@ -739,6 +741,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
// Show dialog with keyguard showing
mGlobalActionsDialogLite.showOrHideDialog(true, true, null, Display.DEFAULT_DISPLAY);
+ // Clear the dismiss override so we don't have behavior after dismissing the dialog
+ mGlobalActionsDialogLite.mDialog.setDismissOverride(null);
assertOneItemOfType(mGlobalActionsDialogLite.mItems,
GlobalActionsDialogLite.SystemUpdateAction.class);
@@ -764,12 +768,15 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
// Show dialog with keyguard showing
mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY);
+ // Clear the dismiss override so we don't have behavior after dismissing the dialog
+ mGlobalActionsDialogLite.mDialog.setDismissOverride(null);
assertNoItemsOfType(mGlobalActionsDialogLite.mItems,
GlobalActionsDialogLite.SystemUpdateAction.class);
// Hide dialog
mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY);
+
}
@Test
@@ -783,6 +790,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalActionsDialogLite.showOrHideDialog(false, true, null, Display.DEFAULT_DISPLAY);
mTestableLooper.processAllMessages();
+ // Clear the dismiss override so we don't have behavior after dismissing the dialog
+ mGlobalActionsDialogLite.mDialog.setDismissOverride(null);
assertThat(mGlobalActionsDialogLite.mDialog.isShowing()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/cursorposition/domain/data/repository/TestCursorPositionRepositoryInstanceProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/cursorposition/domain/data/repository/TestCursorPositionRepositoryInstanceProvider.kt
new file mode 100644
index 000000000000..9f998f3a4e62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/cursorposition/domain/data/repository/TestCursorPositionRepositoryInstanceProvider.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.cursorposition.domain.data.repository
+
+import android.os.Handler
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
+import com.android.systemui.cursorposition.data.repository.InputEventListenerBuilder
+import com.android.systemui.cursorposition.data.repository.InputMonitorBuilder
+import com.android.systemui.cursorposition.data.repository.SingleDisplayCursorPositionRepository
+import com.android.systemui.cursorposition.data.repository.SingleDisplayCursorPositionRepositoryImpl
+
+class TestCursorPositionRepositoryInstanceProvider(
+ private val handler: Handler,
+ private val listenerBuilder: InputEventListenerBuilder,
+ private val inputMonitorBuilder: InputMonitorBuilder,
+) : PerDisplayInstanceProviderWithTeardown<SingleDisplayCursorPositionRepository> {
+
+ override fun destroyInstance(instance: SingleDisplayCursorPositionRepository) {
+ instance.destroy()
+ }
+
+ override fun createInstance(displayId: Int): SingleDisplayCursorPositionRepository {
+ return SingleDisplayCursorPositionRepositoryImpl(
+ displayId,
+ handler,
+ listenerBuilder,
+ inputMonitorBuilder,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 318e5c716ca7..8465345a0bdd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -20,8 +20,10 @@ import android.app.role.mockRoleManager
import android.content.applicationContext
import android.content.res.mainResources
import android.hardware.input.fakeInputManager
+import android.os.fakeExecutorHandler
import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.keyboard.shortcut.data.repository.AppLaunchDataRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
@@ -33,6 +35,7 @@ import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCust
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperInputDeviceRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.data.repository.UserVisibleAppsRepository
import com.android.systemui.keyboard.shortcut.data.source.AccessibilityShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
@@ -44,6 +47,7 @@ import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomiz
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCustomizationModeInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.fakes.FakeLauncherApps
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperExclusions
import com.android.systemui.keyboard.shortcut.ui.ShortcutCustomizationDialogStarter
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
@@ -247,3 +251,15 @@ val Kosmos.shortcutCustomizationViewModelFactory by
}
}
}
+
+val Kosmos.fakeLauncherApps by Kosmos.Fixture { FakeLauncherApps() }
+
+val Kosmos.userVisibleAppsRepository by
+ Kosmos.Fixture {
+ UserVisibleAppsRepository(
+ userTracker,
+ fakeExecutor,
+ fakeExecutorHandler,
+ fakeLauncherApps.launcherApps,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/fakes/FakeLauncherApps.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/fakes/FakeLauncherApps.kt
new file mode 100644
index 000000000000..f0c4a357b974
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/fakes/FakeLauncherApps.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.systemui.keyboard.shortcut.fakes
+
+import android.content.ComponentName
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+
+class FakeLauncherApps {
+
+ private val activityListPerUser: MutableMap<Int, MutableList<LauncherActivityInfo>> =
+ mutableMapOf()
+ private val callbacks: MutableList<LauncherApps.Callback> = mutableListOf()
+
+ val launcherApps: LauncherApps = mock {
+ on { getActivityList(anyOrNull(), any()) }
+ .then {
+ val userHandle = it.getArgument<UserHandle>(1)
+
+ activityListPerUser.getOrDefault(userHandle.identifier, emptyList())
+ }
+ on { registerCallback(any(), any()) }
+ .then {
+ val callback = it.getArgument<LauncherApps.Callback>(0)
+
+ callbacks.add(callback)
+ }
+ on { unregisterCallback(any()) }
+ .then {
+ val callback = it.getArgument<LauncherApps.Callback>(0)
+
+ callbacks.remove(callback)
+ }
+ }
+
+ fun installPackageForUser(packageName: String, className: String, userHandle: UserHandle) {
+ val launcherActivityInfo: LauncherActivityInfo = mock {
+ on { componentName }
+ .thenReturn(ComponentName(/* pkg= */ packageName, /* cls= */ className))
+ }
+
+ if (!activityListPerUser.containsKey(userHandle.identifier)) {
+ activityListPerUser[userHandle.identifier] = mutableListOf()
+ }
+
+ activityListPerUser[userHandle.identifier]!!.add(launcherActivityInfo)
+
+ callbacks.forEach { it.onPackageAdded(/* pkg= */ packageName, userHandle) }
+ }
+
+ fun uninstallPackageForUser(packageName: String, className: String, userHandle: UserHandle) {
+ activityListPerUser[userHandle.identifier]?.removeIf {
+ it.componentName.packageName == packageName && it.componentName.className == className
+ }
+
+ callbacks.forEach { it.onPackageRemoved(/* pkg= */ packageName, userHandle) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index 406b5cb11bb4..19aaa285b6a7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -22,14 +22,14 @@ import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
val Kosmos.keyguardBlueprintInteractor by
Kosmos.Fixture {
KeyguardBlueprintInteractor(
keyguardBlueprintRepository = keyguardBlueprintRepository,
applicationScope = applicationCoroutineScope,
- shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
configurationInteractor = configurationInteractor,
fingerprintPropertyInteractor = fingerprintPropertyInteractor,
smartspaceSection = keyguardSmartspaceSection,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
index 76e2cc8b7bd0..384a071b5155 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
@@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.util.mockito.mock
val Kosmos.keyguardSmartspaceViewModel by
@@ -29,6 +29,6 @@ val Kosmos.keyguardSmartspaceViewModel by
smartspaceController = mock(),
keyguardClockViewModel = keyguardClockViewModel,
smartspaceInteractor = keyguardSmartspaceInteractor,
- shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 32a30502a370..d06d4ca5597d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -69,7 +69,6 @@ val Kosmos.shadeInteractorImpl by
userSetupRepository = userSetupRepository,
userSwitcherInteractor = userSwitcherInteractor,
baseShadeInteractor = baseShadeInteractor,
- shadeModeInteractor = shadeModeInteractor,
)
}
var Kosmos.notificationElement: NotificationShadeElement by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 55e35f2b2703..23251d27cff9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarou
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
@@ -33,6 +34,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel:
notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
+ shadeModeInteractor = shadeModeInteractor,
disableFlagsInteractor = disableFlagsInteractor,
mediaCarouselInteractor = mediaCarouselInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 05fc6bc869ca..c2500c8ae7fa 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -559,6 +559,9 @@ public class CameraServiceProxy extends SystemService
@Override
public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
List<Rect> unrestricted) { }
+
+ @Override
+ public void onDesktopModeEligibleChanged(int displayId) { }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index bbf7732c9596..7059c83d60b2 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -44,6 +44,7 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Collection;
import java.util.HashSet;
@@ -53,6 +54,9 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@@ -71,6 +75,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/** The duration of wakelocks acquired during HAL callbacks */
private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000;
+ /** The timeout of open session request */
+ @VisibleForTesting static final long OPEN_SESSION_REQUEST_TIMEOUT_SECONDS = 10;
+
/*
* Internal interface used to invoke client callbacks.
*/
@@ -81,6 +88,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/** The context of the service. */
private final Context mContext;
+ /** The shared executor service for handling session operation timeout. */
+ private final ScheduledExecutorService mSessionTimeoutExecutor;
+
/** The proxy to talk to the Context Hub HAL for endpoint communication. */
private final IEndpointCommunication mHubInterface;
@@ -119,6 +129,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
private SessionState mSessionState = SessionState.PENDING;
+ private ScheduledFuture<?> mSessionOpenTimeoutFuture;
+
private final boolean mRemoteInitiated;
/**
@@ -151,6 +163,17 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mSessionState = state;
}
+ public void setSessionOpenTimeoutFuture(ScheduledFuture<?> future) {
+ mSessionOpenTimeoutFuture = future;
+ }
+
+ public void cancelSessionOpenTimeoutFuture() {
+ if (mSessionOpenTimeoutFuture != null) {
+ mSessionOpenTimeoutFuture.cancel(false);
+ }
+ mSessionOpenTimeoutFuture = null;
+ }
+
public boolean isActive() {
return mSessionState == SessionState.ACTIVE;
}
@@ -240,7 +263,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
@NonNull IContextHubEndpointCallback callback,
String packageName,
String attributionTag,
- ContextHubTransactionManager transactionManager) {
+ ContextHubTransactionManager transactionManager,
+ ScheduledExecutorService sessionTimeoutExecutor) {
mContext = context;
mHubInterface = hubInterface;
mEndpointManager = endpointManager;
@@ -250,6 +274,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mPackageName = packageName;
mAttributionTag = attributionTag;
mTransactionManager = transactionManager;
+ mSessionTimeoutExecutor = sessionTimeoutExecutor;
mPid = Binder.getCallingPid();
mUid = Binder.getCallingUid();
@@ -352,6 +377,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
try {
mHubInterface.endpointSessionOpenComplete(sessionId);
+ info.cancelSessionOpenTimeoutFuture();
info.setSessionState(Session.SessionState.ACTIVE);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
@@ -636,9 +662,10 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
}
// Check & handle error cases for duplicated session id.
- final boolean existingSession;
- final boolean existingSessionActive;
synchronized (mOpenSessionLock) {
+ final boolean existingSession;
+ final boolean existingSessionActive;
+
if (hasSessionId(sessionId)) {
existingSession = true;
existingSessionActive = mSessionMap.get(sessionId).isActive();
@@ -652,19 +679,23 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
} else {
existingSession = false;
existingSessionActive = false;
- mSessionMap.put(sessionId, new Session(initiator, true));
+ Session pendingSession = new Session(initiator, true);
+ pendingSession.setSessionOpenTimeoutFuture(
+ mSessionTimeoutExecutor.schedule(
+ () -> onEndpointSessionOpenRequestTimeout(sessionId),
+ OPEN_SESSION_REQUEST_TIMEOUT_SECONDS,
+ TimeUnit.SECONDS));
+ mSessionMap.put(sessionId, pendingSession);
}
- }
- if (existingSession) {
- if (existingSessionActive) {
- // Existing session is already active, call onSessionOpenComplete.
- openSessionRequestComplete(sessionId);
+ if (existingSession) {
+ if (existingSessionActive) {
+ // Existing session is already active, call onSessionOpenComplete.
+ openSessionRequestComplete(sessionId);
+ }
+ // Silence this request. The session open timeout future will handle clean up.
return Optional.empty();
}
- // Reject the session open request for now. Consider invalidating previous pending
- // session open request based on timeout.
- return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
}
boolean success =
@@ -679,6 +710,20 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
return success ? Optional.empty() : Optional.of(reason);
}
+ private void onEndpointSessionOpenRequestTimeout(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ Session s = mSessionMap.get(sessionId);
+ if (s == null || s.isActive()) {
+ return;
+ }
+ Log.w(
+ TAG,
+ "onEndpointSessionOpenRequestTimeout: " + "clean up session, id: " + sessionId);
+ cleanupSessionResources(sessionId);
+ mEndpointManager.halCloseEndpointSessionNoThrow(sessionId, Reason.TIMEOUT);
+ }
+ }
+
private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
synchronized (mOpenSessionLock) {
if (!isSessionActive(sessionId)) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index e1561599517d..0dc1b832f5a4 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -46,6 +46,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.function.Consumer;
/**
@@ -112,6 +114,9 @@ import java.util.function.Consumer;
/** The interface for endpoint communication (retrieved from HAL in init()) */
private IEndpointCommunication mHubInterface = null;
+ /** Thread pool executor for handling timeout */
+ private final ScheduledExecutorService mSessionTimeoutExecutor;
+
/*
* The list of previous registration records.
*/
@@ -154,15 +159,31 @@ import java.util.function.Consumer;
}
}
- /* package */ ContextHubEndpointManager(
+ @VisibleForTesting
+ ContextHubEndpointManager(
Context context,
IContextHubWrapper contextHubProxy,
HubInfoRegistry hubInfoRegistry,
- ContextHubTransactionManager transactionManager) {
+ ContextHubTransactionManager transactionManager,
+ ScheduledExecutorService scheduledExecutorService) {
mContext = context;
mContextHubProxy = contextHubProxy;
mHubInfoRegistry = hubInfoRegistry;
mTransactionManager = transactionManager;
+ mSessionTimeoutExecutor = scheduledExecutorService;
+ }
+
+ /* package */ ContextHubEndpointManager(
+ Context context,
+ IContextHubWrapper contextHubProxy,
+ HubInfoRegistry hubInfoRegistry,
+ ContextHubTransactionManager transactionManager) {
+ this(
+ context,
+ contextHubProxy,
+ hubInfoRegistry,
+ transactionManager,
+ new ScheduledThreadPoolExecutor(1));
}
/**
@@ -264,7 +285,8 @@ import java.util.function.Consumer;
callback,
packageName,
attributionTag,
- mTransactionManager);
+ mTransactionManager,
+ mSessionTimeoutExecutor);
broker.register();
mEndpointMap.put(endpointId, broker);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6ce1746ed3f6..c1e46a68b733 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1877,73 +1877,35 @@ public class NotificationManagerService extends SystemService {
}
};
- private void unclassifyNotificationsForUser(final int userId) {
- if (DBG) {
- Slog.v(TAG, "unclassifyForUser: " + userId);
- }
- unclassifyNotificationsFiltered((r) -> r.getUserId() == userId);
+ private void applyNotificationUpdateForUser(final int userId,
+ NotificationUpdate notificationUpdate) {
+ applyUpdateForNotificationsFiltered((r) -> r.getUserId() == userId,
+ notificationUpdate);
}
- private void unclassifyNotificationsForUid(final int userId, @NonNull final String pkg) {
- if (DBG) {
- Slog.v(TAG, "unclassifyForUid userId: " + userId + " pkg: " + pkg);
- }
- unclassifyNotificationsFiltered((r) ->
+ private void applyNotificationUpdateForUid(final int userId, @NonNull final String pkg,
+ NotificationUpdate notificationUpdate) {
+ applyUpdateForNotificationsFiltered((r) ->
r.getUserId() == userId
- && Objects.equals(r.getSbn().getPackageName(), pkg));
+ && Objects.equals(r.getSbn().getPackageName(), pkg),
+ notificationUpdate);
}
- private void unclassifyNotificationsForUserAndType(final int userId,
- final @Types int bundleType) {
- if (DBG) {
- Slog.v(TAG,
- "unclassifyForUserAndType userId: " + userId + " bundleType: " + bundleType);
- }
+ private void applyNotificationUpdateForUserAndChannelType(final int userId,
+ final @Types int bundleType, NotificationUpdate notificationUpdate) {
final String bundleChannelId = NotificationChannel.getChannelIdForBundleType(bundleType);
- unclassifyNotificationsFiltered((r) ->
+ applyUpdateForNotificationsFiltered((r) ->
r.getUserId() == userId
&& r.getChannel() != null
- && Objects.equals(bundleChannelId, r.getChannel().getId()));
+ && Objects.equals(bundleChannelId, r.getChannel().getId()),
+ notificationUpdate);
}
- private void unclassifyNotificationsFiltered(Predicate<NotificationRecord> filter) {
- if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
- return;
- }
- synchronized (mNotificationLock) {
- for (int i = 0; i < mEnqueuedNotifications.size(); i++) {
- final NotificationRecord r = mEnqueuedNotifications.get(i);
- if (filter.test(r)) {
- unclassifyNotificationLocked(r);
- }
- }
-
- for (int i = 0; i < mNotificationList.size(); i++) {
- final NotificationRecord r = mNotificationList.get(i);
- if (filter.test(r)) {
- unclassifyNotificationLocked(r);
- }
- }
- }
- }
-
- @GuardedBy("mNotificationLock")
- private void unclassifyNotificationLocked(@NonNull final NotificationRecord r) {
- if (DBG) {
- Slog.v(TAG, "unclassifyNotification: " + r);
- }
- // Only NotificationRecord's mChannel is updated when bundled, the Notification
- // mChannelId will always be the original channel.
- String origChannelId = r.getNotification().getChannelId();
- NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
- r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
- String currChannelId = r.getChannel().getId();
- boolean isClassified = NotificationChannel.SYSTEM_RESERVED_IDS.contains(currChannelId);
- if (originalChannel != null && !origChannelId.equals(currChannelId) && isClassified) {
- r.updateNotificationChannel(originalChannel);
- mGroupHelper.onNotificationUnbundled(r,
- GroupHelper.isOriginalGroupSummaryPresent(r, mSummaryByGroupKey));
- }
+ private void applyNotificationUpdateForUserAndType(final int userId,
+ final @Types int bundleType, NotificationUpdate notificationUpdate) {
+ applyUpdateForNotificationsFiltered(
+ (r) -> r.getUserId() == userId && r.getBundleType() == bundleType,
+ notificationUpdate);
}
@VisibleForTesting
@@ -1956,7 +1918,7 @@ public class NotificationManagerService extends SystemService {
if (r == null) {
return;
}
- unclassifyNotificationLocked(r);
+ unclassifyNotificationLocked(r, true);
}
}
@@ -1974,50 +1936,36 @@ public class NotificationManagerService extends SystemService {
}
}
- private void reclassifyNotificationsFiltered(Predicate<NotificationRecord> filter) {
- if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
- return;
- }
- synchronized (mNotificationLock) {
- for (int i = 0; i < mEnqueuedNotifications.size(); i++) {
- final NotificationRecord r = mEnqueuedNotifications.get(i);
- if (filter.test(r)) {
- reclassifyNotificationLocked(r, false);
- }
- }
-
- for (int i = 0; i < mNotificationList.size(); i++) {
- final NotificationRecord r = mNotificationList.get(i);
- if (filter.test(r)) {
- reclassifyNotificationLocked(r, true);
- }
- }
- }
- }
-
- private void reclassifyNotificationsForUserAndType(final int userId,
- final @Types int bundleType) {
+ @GuardedBy("mNotificationLock")
+ private void unclassifyNotificationLocked(@NonNull final NotificationRecord r,
+ boolean isPosted) {
if (DBG) {
- Slog.v(TAG, "reclassifyNotificationsForUserAndType userId: " + userId + " bundleType: "
- + bundleType);
+ Slog.v(TAG, "unclassifyNotification: " + r);
}
- reclassifyNotificationsFiltered(
- (r) -> r.getUserId() == userId && r.getBundleType() == bundleType);
- }
-
- private void reclassifyNotificationsForUid(final int userId, final String pkg) {
- if (DBG) {
- Slog.v(TAG, "reclassifyNotificationsForUid userId: " + userId + " pkg: " + pkg);
+ // Only NotificationRecord's mChannel is updated when bundled, the Notification
+ // mChannelId will always be the original channel.
+ String origChannelId = r.getNotification().getChannelId();
+ NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
+ String currChannelId = r.getChannel().getId();
+ boolean isClassified = NotificationChannel.SYSTEM_RESERVED_IDS.contains(currChannelId);
+ if (originalChannel != null && !origChannelId.equals(currChannelId) && isClassified) {
+ r.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(r,
+ GroupHelper.isOriginalGroupSummaryPresent(r, mSummaryByGroupKey));
}
- reclassifyNotificationsFiltered((r) ->
- r.getUserId() == userId && Objects.equals(r.getSbn().getPackageName(), pkg));
}
- private void reclassifyNotificationsForUser(final int userId) {
- if (DBG) {
- Slog.v(TAG, "reclassifyAllNotificationsForUser: " + userId);
- }
- reclassifyNotificationsFiltered((r) -> r.getUserId() == userId);
+ @GuardedBy("mNotificationLock")
+ private void unsummarizeNotificationLocked(@NonNull final NotificationRecord r,
+ boolean isPosted) {
+ Bundle signals = new Bundle();
+ signals.putString(KEY_SUMMARIZATION, null);
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+ r.getSbn().getUserId());
+ r.addAdjustment(adjustment);
+ mRankingHandler.requestSort();
+
}
@GuardedBy("mNotificationLock")
@@ -2043,6 +1991,33 @@ public class NotificationManagerService extends SystemService {
}
}
+ /**
+ * Given a filter and a function to update a notification record, runs that function on all
+ * enqueued and posted notifications that match the filter
+ */
+ private void applyUpdateForNotificationsFiltered(Predicate<NotificationRecord> filter,
+ NotificationUpdate notificationUpdate) {
+ synchronized (mNotificationLock) {
+ for (int i = 0; i < mEnqueuedNotifications.size(); i++) {
+ final NotificationRecord r = mEnqueuedNotifications.get(i);
+ if (filter.test(r)) {
+ notificationUpdate.apply(r, false);
+ }
+ }
+
+ for (int i = 0; i < mNotificationList.size(); i++) {
+ final NotificationRecord r = mNotificationList.get(i);
+ if (filter.test(r)) {
+ notificationUpdate.apply(r, true);
+ }
+ }
+ }
+ }
+
+ private interface NotificationUpdate {
+ void apply(NotificationRecord r, boolean isPosted);
+ }
+
NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
@Nullable
@Override
@@ -4475,9 +4450,11 @@ public class NotificationManagerService extends SystemService {
public void allowAssistantAdjustment(String adjustmentType) {
checkCallerIsSystemOrSystemUiOrShell();
mAssistants.allowAdjustmentType(adjustmentType);
+ int userId = UserHandle.getUserId(Binder.getCallingUid());
if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
if (KEY_TYPE.equals(adjustmentType)) {
- reclassifyNotificationsForUser(UserHandle.getUserId(Binder.getCallingUid()));
+ applyNotificationUpdateForUser(userId,
+ NotificationManagerService.this::reclassifyNotificationLocked);
}
}
handleSavePolicyFile();
@@ -4488,9 +4465,17 @@ public class NotificationManagerService extends SystemService {
public void disallowAssistantAdjustment(String adjustmentType) {
checkCallerIsSystemOrSystemUiOrShell();
mAssistants.disallowAdjustmentType(adjustmentType);
+ int userId = UserHandle.getUserId(Binder.getCallingUid());
if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
if (KEY_TYPE.equals(adjustmentType)) {
- unclassifyNotificationsForUser(UserHandle.getUserId(Binder.getCallingUid()));
+ applyNotificationUpdateForUser(userId,
+ NotificationManagerService.this::unclassifyNotificationLocked);
+ }
+ }
+ if (nmSummarizationUi() || nmSummarization()) {
+ if (KEY_SUMMARIZATION.equals(adjustmentType)) {
+ applyNotificationUpdateForUser(userId,
+ NotificationManagerService.this::unsummarizeNotificationLocked);
}
}
handleSavePolicyFile();
@@ -4539,11 +4524,13 @@ public class NotificationManagerService extends SystemService {
mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled);
if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
if (enabled) {
- reclassifyNotificationsForUserAndType(
- UserHandle.getUserId(Binder.getCallingUid()), type);
+ applyNotificationUpdateForUserAndType(
+ UserHandle.getUserId(Binder.getCallingUid()), type,
+ NotificationManagerService.this::reclassifyNotificationLocked);
} else {
- unclassifyNotificationsForUserAndType(
- UserHandle.getUserId(Binder.getCallingUid()), type);
+ applyNotificationUpdateForUserAndChannelType(
+ UserHandle.getUserId(Binder.getCallingUid()), type,
+ NotificationManagerService.this::unclassifyNotificationLocked);
}
}
handleSavePolicyFile();
@@ -4569,11 +4556,17 @@ public class NotificationManagerService extends SystemService {
if (notificationClassificationUi() && notificationRegroupOnClassification()
&& key.equals(KEY_TYPE)) {
if (enabled) {
- reclassifyNotificationsForUid(UserHandle.getUserId(Binder.getCallingUid()),
- pkg);
+ applyNotificationUpdateForUid(UserHandle.getUserId(Binder.getCallingUid()),
+ pkg, NotificationManagerService.this::reclassifyNotificationLocked);
} else {
- unclassifyNotificationsForUid(UserHandle.getUserId(Binder.getCallingUid()),
- pkg);
+ applyNotificationUpdateForUid(UserHandle.getUserId(Binder.getCallingUid()),
+ pkg, NotificationManagerService.this::unclassifyNotificationLocked);
+ }
+ }
+ if (nmSummarization() || nmSummarizationUi()) {
+ if (KEY_SUMMARIZATION.equals(key) && !enabled) {
+ applyNotificationUpdateForUid(UserHandle.getUserId(Binder.getCallingUid()),
+ pkg, NotificationManagerService.this::unsummarizeNotificationLocked);
}
}
handleSavePolicyFile();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index cec5a93a2a15..700f6fafe2d7 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -999,7 +999,7 @@ public final class NotificationRecord {
return null;
}
- public String getSummarization() {
+ public @Nullable String getSummarization() {
if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) {
return mSummarization;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index af788ea6ccdb..e00d80f860b0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3107,8 +3107,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mInternalProgress = 0.5f;
computeProgressLocked(true);
}
+ final File libDir = new File(stageDir, NativeLibraryHelper.LIB_DIR_NAME);
+ if (!mayInheritNativeLibs()) {
+ // Start from a clean slate
+ NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
+ }
+ // Skip native libraries processing for archival installation.
+ if (isArchivedInstallation()) {
+ return;
+ }
extractNativeLibraries(
- mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());
+ mPackageLite, libDir, params.abiOverride);
}
}
}
@@ -4505,21 +4514,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
}
- private void extractNativeLibraries(PackageLite packageLite, File packageDir,
- String abiOverride, boolean inherit)
+ private void extractNativeLibraries(PackageLite packageLite, File libDir,
+ String abiOverride)
throws PackageManagerException {
Objects.requireNonNull(packageLite);
- final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
- if (!inherit) {
- // Start from a clean slate
- NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
- }
-
- // Skip native libraries processing for archival installation.
- if (isArchivedInstallation()) {
- return;
- }
-
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(packageLite);
diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
index 7cd9bdbc662c..b4ca7845ffee 100644
--- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
+++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
@@ -505,7 +505,9 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto
for (int i = 0; i < mHistoryFiles.size(); i++) {
size += (int) mHistoryFiles.get(i).atomicFile.getBaseFile().length();
}
- while (size > mMaxHistorySize) {
+ // Trim until the directory size is within the limit or there is just one most
+ // recent file left in the directory
+ while (size > mMaxHistorySize && mHistoryFiles.size() > 1) {
BatteryHistoryFile oldest = mHistoryFiles.get(0);
int length = (int) oldest.atomicFile.getBaseFile().length();
oldest.atomicFile.delete();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 20b0f58d4fd9..1d7247330b7a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -98,7 +98,6 @@ import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
@@ -109,7 +108,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.hasWindowExtensionsEnabled;
import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
@@ -117,9 +115,7 @@ import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONTAINERS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_FOCUS;
@@ -135,8 +131,6 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.FINISHING;
@@ -364,7 +358,6 @@ import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.window.flags.Flags;
@@ -7208,40 +7201,6 @@ final class ActivityRecord extends WindowToken {
return candidate;
}
- @Override
- public SurfaceControl getAnimationLeashParent() {
- // For transitions in the root pinned task (menu activity) we just let them occur as a child
- // of the root pinned task.
- // All normal app transitions take place in an animation layer which is below the root
- // pinned task but may be above the parent tasks of the given animating apps by default.
- // When a new hierarchical animation is enabled, we just let them occur as a child of the
- // parent task, i.e. the hierarchy of the surfaces is unchanged.
- if (inPinnedWindowingMode()) {
- return getRootTask().getSurfaceControl();
- } else {
- return super.getAnimationLeashParent();
- }
- }
-
- @VisibleForTesting
- boolean shouldAnimate() {
- return task == null || task.shouldAnimate();
- }
-
- /**
- * Creates a layer to apply crop to an animation.
- */
- private SurfaceControl createAnimationBoundsLayer(Transaction t) {
- ProtoLog.i(WM_DEBUG_APP_TRANSITIONS_ANIM, "Creating animation bounds layer");
- final SurfaceControl.Builder builder = makeAnimationLeash()
- .setParent(getAnimationLeashParent())
- .setName(getSurfaceControl() + " - animation-bounds")
- .setCallsite("ActivityRecord.createAnimationBoundsLayer");
- final SurfaceControl boundsLayer = builder.build();
- t.show(boundsLayer);
- return boundsLayer;
- }
-
boolean isTransitionForward() {
return (mStartingData != null && mStartingData.mIsTransitionForward)
|| mDisplayContent.isNextTransitionForward();
@@ -7253,25 +7212,6 @@ final class ActivityRecord extends WindowToken {
}
@Override
- public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
- // If the animation needs to be cropped then an animation bounds layer is created as a
- // child of the root pinned task or animation layer. The leash is then reparented to this
- // new layer.
- if (mNeedsAnimationBoundsLayer) {
- mTmpRect.setEmpty();
- task.getBounds(mTmpRect);
- mAnimationBoundsLayer = createAnimationBoundsLayer(t);
-
- // Crop to root task bounds.
- t.setLayer(leash, 0);
- t.setLayer(mAnimationBoundsLayer, getLastLayer());
-
- // Reparent leash to animation bounds layer.
- t.reparent(leash, mAnimationBoundsLayer);
- }
- }
-
- @Override
boolean showSurfaceOnCreation() {
return false;
}
@@ -7310,74 +7250,6 @@ final class ActivityRecord extends WindowToken {
return mLastSurfaceShowing;
}
- @Override
- public void onAnimationLeashLost(Transaction t) {
- super.onAnimationLeashLost(t);
- if (mAnimationBoundsLayer != null) {
- t.remove(mAnimationBoundsLayer);
- mAnimationBoundsLayer = null;
- }
-
- mNeedsAnimationBoundsLayer = false;
- }
-
- @Override
- protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
- super.onAnimationFinished(type, anim);
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");
- mTransit = TRANSIT_OLD_UNSET;
- mTransitFlags = 0;
-
- setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
- "ActivityRecord");
-
- setClientVisible(isVisible() || mVisibleRequested);
-
- getDisplayContent().computeImeTargetIfNeeded(this);
-
- ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
- + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(),
- isStartingWindowDisplayed());
-
- // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
- // traverse the copy.
- final ArrayList<WindowState> children = new ArrayList<>(mChildren);
- children.forEach(WindowState::onExitAnimationDone);
- // The starting window could transfer to another activity after app transition started, in
- // that case the latest top activity might not receive exit animation done callback if the
- // starting window didn't applied exit animation success. Notify animation finish to the
- // starting window if needed.
- if (task != null && startingMoved) {
- final WindowState transferredStarting = task.getWindow(w ->
- w.mAttrs.type == TYPE_APPLICATION_STARTING);
- if (transferredStarting != null && transferredStarting.mAnimatingExit
- && !transferredStarting.isSelfAnimating(0 /* flags */,
- ANIMATION_TYPE_WINDOW_ANIMATION)) {
- transferredStarting.onExitAnimationDone();
- }
- }
-
- scheduleAnimation();
-
- // Schedule to handle the stopping and finishing activities which the animation is done
- // because the activities which were animating have not been stopped yet.
- mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void clearAnimatingFlags() {
- boolean wallpaperMightChange = false;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState win = mChildren.get(i);
- wallpaperMightChange |= win.clearAnimatingFlags();
- }
- if (wallpaperMightChange) {
- requestUpdateWallpaperIfNeeded();
- }
- }
-
public @TransitionOldType int getTransit() {
return mTransit;
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 03ba1a51ad7b..61e8e09bc035 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -242,20 +241,18 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
@NonNull Task launchingTask) {
if (existingTaskActivity == null || launchingActivity == null) return false;
return (existingTaskActivity.packageName == launchingActivity.packageName)
- && isLaunchingNewTask(launchingActivity.launchMode,
- launchingTask.getBaseIntent().getFlags())
+ && isLaunchingNewSingleTask(launchingActivity.launchMode)
&& isClosingExitingInstance(launchingTask.getBaseIntent().getFlags());
}
/**
- * Returns true if the launch mode or intent will result in a new task being created for the
+ * Returns true if the launch mode will result in a single new task being created for the
* activity.
*/
- private boolean isLaunchingNewTask(int launchMode, int intentFlags) {
+ private boolean isLaunchingNewSingleTask(int launchMode) {
return launchMode == LAUNCH_SINGLE_TASK
|| launchMode == LAUNCH_SINGLE_INSTANCE
- || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
- || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0;
+ || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f9242fbdfc9..16caec81f5f8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4982,22 +4982,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return win != null;
}
- /**
- * Callbacks when the given type of {@link WindowContainer} animation finished running in the
- * hierarchy.
- */
- void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
- if (mImeScreenshot != null) {
- ProtoLog.i(WM_DEBUG_IME,
- "onWindowAnimationFinished, wc=%s, type=%s, imeSnapshot=%s, target=%s",
- wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot,
- mImeScreenshot.getImeTarget());
- }
- if ((type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
- removeImeSurfaceByTarget(wc);
- }
- }
-
// TODO: Super unexpected long method that should be broken down...
void applySurfaceChangesTransaction() {
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9eb0574d87c..dbae9c4b3a0f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1882,6 +1882,9 @@ public class DisplayPolicy {
final boolean isSystemDecorationsSupported =
mDisplayContent.isSystemDecorationsSupported();
final boolean isHomeSupported = mDisplayContent.isHomeSupported();
+ final boolean eligibleForDesktopMode =
+ isSystemDecorationsSupported && (mDisplayContent.isDefaultDisplay
+ || mDisplayContent.allowContentModeSwitch());
mHandler.post(() -> {
if (isSystemDecorationsSupported) {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -1896,6 +1899,10 @@ public class DisplayPolicy {
wpMgr.onDisplayAddSystemDecorations(displayId);
}
}
+ if (eligibleForDesktopMode) {
+ mService.mDisplayNotificationController.dispatchDesktopModeEligibleChanged(
+ displayId);
+ }
});
} else {
mHandler.post(() -> {
@@ -1926,6 +1933,8 @@ public class DisplayPolicy {
if (wpMgr != null) {
wpMgr.onDisplayRemoveSystemDecorations(displayId);
}
+ mService.mDisplayNotificationController.dispatchDesktopModeEligibleChanged(
+ displayId);
final NotificationManagerInternal notificationManager =
LocalServices.getService(NotificationManagerInternal.class);
if (notificationManager != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index d90fff229cd9..d705274b62e7 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -133,4 +133,15 @@ class DisplayWindowListenerController {
}
mDisplayListeners.finishBroadcast();
}
+
+ void dispatchDesktopModeEligibleChanged(int displayId) {
+ int count = mDisplayListeners.beginBroadcast();
+ for (int i = 0; i < count; ++i) {
+ try {
+ mDisplayListeners.getBroadcastItem(i).onDesktopModeEligibleChanged(displayId);
+ } catch (RemoteException e) {
+ }
+ }
+ mDisplayListeners.finishBroadcast();
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ec17d131958b..3cce17242648 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5764,15 +5764,16 @@ class Task extends TaskFragment {
return false;
}
- // If we have a watcher, preflight the move before committing to it. First check
- // for *other* available tasks, but if none are available, then try again allowing the
- // current task to be selected.
+ // If we have a watcher, preflight the move before committing to it.
+ // Checks for other available tasks; however, if none are available, skips because this
+ // is the bottommost task.
if (mAtmService.mController != null && isTopRootTaskInDisplayArea()) {
- ActivityRecord next = topRunningActivity(null, task.mTaskId);
- if (next == null) {
- next = topRunningActivity(null, INVALID_TASK_ID);
- }
+ final ActivityRecord next = getDisplayArea().getActivity(
+ a -> isTopRunning(a, task.mTaskId, null /* notTop */));
if (next != null) {
+ if (next.isState(RESUMED)) {
+ return true;
+ }
// ask watcher if this is allowed
boolean moveOK = true;
try {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5b4870b0c0c7..b1422c20e516 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -55,14 +55,12 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.CallSuper;
-import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
-import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
@@ -105,7 +103,6 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -218,14 +215,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
protected final WindowManagerService mWmService;
final TransitionController mTransitionController;
- /**
- * Sources which triggered a surface animation on this container. An animation target can be
- * promoted to higher level, for example, from a set of {@link ActivityRecord}s to
- * {@link Task}. In this case, {@link ActivityRecord}s are set on this variable while
- * the animation is running, and reset after finishing it.
- */
- private final ArraySet<WindowContainer> mSurfaceAnimationSources = new ArraySet<>();
-
private final Point mTmpPos = new Point();
protected final Point mLastSurfacePosition = new Point();
protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0;
@@ -279,17 +268,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
int mTransitFlags;
- /** Layer used to constrain the animation to a container's stack bounds. */
- SurfaceControl mAnimationBoundsLayer;
-
- /** Whether this container needs to create mAnimationBoundsLayer for cropping animations. */
- boolean mNeedsAnimationBoundsLayer;
-
- /**
- * This gets used during some open/close transitions as well as during a change transition
- * where it represents the starting-state snapshot.
- */
- final Point mTmpPoint = new Point();
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
@@ -2961,7 +2939,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
void cancelAnimation() {
- doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());
mSurfaceAnimator.cancelAnimation();
}
@@ -2992,10 +2969,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
|| (getParent() != null && getParent().inPinnedWindowingMode());
}
- ArraySet<WindowContainer> getAnimationSources() {
- return mSurfaceAnimationSources;
- }
-
@Override
public Builder makeAnimationLeash() {
return makeSurface().setContainerLayer();
@@ -3094,21 +3067,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return mAnimationLeash;
}
- private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
- for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {
- mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);
- }
- mSurfaceAnimationSources.clear();
- if (mDisplayContent != null) {
- mDisplayContent.onWindowAnimationFinished(this, type);
- }
- }
-
/**
* Called when an animation has finished running.
*/
protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
- doAnimationFinished(type, anim);
mWmService.onAnimationFinished();
}
@@ -3821,50 +3783,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return true;
}
- private class AnimationRunnerBuilder {
- /**
- * Runs when the surface stops animating
- */
- private final List<Runnable> mOnAnimationFinished = new LinkedList<>();
- /**
- * Runs when the animation is cancelled but the surface is still animating
- */
- private final List<Runnable> mOnAnimationCancelled = new LinkedList<>();
-
- private void setTaskBackgroundColor(@ColorInt int backgroundColor) {
- TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
-
- if (taskDisplayArea != null && backgroundColor != Color.TRANSPARENT) {
- taskDisplayArea.setBackgroundColor(backgroundColor);
-
- // Atomic counter to make sure the clearColor callback is only called one.
- // It will be called twice in the case we cancel the animation without restart
- // (in that case it will run as the cancel and finished callbacks).
- final AtomicInteger callbackCounter = new AtomicInteger(0);
- final Runnable clearBackgroundColorHandler = () -> {
- if (callbackCounter.getAndIncrement() == 0) {
- taskDisplayArea.clearBackgroundColor();
- }
- };
-
- // We want to make sure this is called both when the surface stops animating and
- // also when an animation is cancelled (i.e. animation is replaced by another
- // animation but and so the surface is still animating)
- mOnAnimationFinished.add(clearBackgroundColorHandler);
- mOnAnimationCancelled.add(clearBackgroundColorHandler);
- }
- }
-
- private IAnimationStarter build() {
- return (Transaction t, AnimationAdapter adapter, boolean hidden,
- @AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {
- startAnimation(getPendingTransaction(), adapter, !isVisible(), type,
- (animType, anim) -> mOnAnimationFinished.forEach(Runnable::run),
- () -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim);
- };
- }
- }
-
private interface IAnimationStarter {
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d43aba0d218d..a03b765cae6a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4697,40 +4697,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return super.handleCompleteDeferredRemoval();
}
- boolean clearAnimatingFlags() {
- boolean didSomething = false;
- // We also don't clear the mAnimatingExit flag for windows which have the
- // mRemoveOnExit flag. This indicates an explicit remove request has been issued
- // by the client. We should let animation proceed and not clear this flag or
- // they won't eventually be removed by WindowStateAnimator#finishExit.
- if (!mRemoveOnExit) {
- // Clear mAnimating flag together with mAnimatingExit. When animation
- // changes from exiting to entering, we need to clear this flag until the
- // new animation gets applied, so that isAnimationStarting() becomes true
- // until then.
- // Otherwise applySurfaceChangesTransaction will fail to skip surface
- // placement for this window during this period, one or more frame will
- // show up with wrong position or scale.
- if (mAnimatingExit) {
- mAnimatingExit = false;
- ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=clearAnimatingFlags win=%s",
- this);
- didSomething = true;
- }
- if (mDestroying) {
- mDestroying = false;
- mWmService.mDestroySurface.remove(this);
- didSomething = true;
- }
- }
-
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- didSomething |= (mChildren.get(i)).clearAnimatingFlags();
- }
-
- return didSomething;
- }
-
public boolean isRtl() {
return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 87cd1560509c..992c1183d0c0 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -62,6 +63,7 @@ import org.mockito.junit.MockitoRule;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
@RunWith(AndroidJUnit4.class)
@Presubmit
@@ -97,6 +99,7 @@ public class ContextHubEndpointTest {
private HubInfoRegistry mHubInfoRegistry;
private ContextHubTransactionManager mTransactionManager;
private Context mContext;
+ @Mock private ScheduledExecutorService mMockTimeoutExecutorService;
@Mock private IEndpointCommunication mMockEndpointCommunications;
@Mock private IContextHubWrapper mMockContextHubWrapper;
@Mock private IContextHubEndpointCallback mMockCallback;
@@ -120,7 +123,11 @@ public class ContextHubEndpointTest {
mMockContextHubWrapper, mClientManager, new NanoAppStateManager());
mEndpointManager =
new ContextHubEndpointManager(
- mContext, mMockContextHubWrapper, mHubInfoRegistry, mTransactionManager);
+ mContext,
+ mMockContextHubWrapper,
+ mHubInfoRegistry,
+ mTransactionManager,
+ mMockTimeoutExecutorService);
mEndpointManager.init();
}
@@ -248,14 +255,20 @@ public class ContextHubEndpointTest {
endpoint.getAssignedHubEndpointInfo().getIdentifier(),
targetInfo.getIdentifier(),
ENDPOINT_SERVICE_DESCRIPTOR);
-
verify(mMockCallback)
.onSessionOpenRequest(
SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
// Accept
endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
- verify(mMockEndpointCommunications)
+
+ // Even when timeout happens, there should be no effect on this session
+ ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMockTimeoutExecutorService)
+ .schedule(runnableArgumentCaptor.capture(), anyLong(), any());
+ runnableArgumentCaptor.getValue().run();
+
+ verify(mMockEndpointCommunications, times(1))
.endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
unregisterExampleEndpoint(endpoint);
@@ -331,6 +344,87 @@ public class ContextHubEndpointTest {
}
@Test
+ public void testEndpointSessionOpenRequest_rejectAfterTimeout() throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ // Immediately timeout
+ ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMockTimeoutExecutorService)
+ .schedule(runnableArgumentCaptor.capture(), anyLong(), any());
+ runnableArgumentCaptor.getValue().run();
+
+ // Client's callback shouldn't matter after timeout
+ try {
+ endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+ } catch (IllegalArgumentException ignore) {
+ // This will throw because the session is no longer valid
+ }
+
+ // HAL will receive closeEndpointSession with Timeout as reason
+ verify(mMockEndpointCommunications, times(1))
+ .closeEndpointSession(SESSION_ID_FOR_OPEN_REQUEST, Reason.TIMEOUT);
+ // HAL will not receives open complete notifications
+ verify(mMockEndpointCommunications, never())
+ .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWithinTimeout()
+ throws RemoteException {
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ // Duplicated session open request
+ mEndpointManager.onEndpointSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST,
+ endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+ targetInfo.getIdentifier(),
+ ENDPOINT_SERVICE_DESCRIPTOR);
+
+ // Finally, endpoint approved the session open request
+ endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+ // Client API is only invoked once
+ verify(mMockCallback, times(1))
+ .onSessionOpenRequest(
+ SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+ // HAL still receives two open complete notifications
+ verify(mMockEndpointCommunications, times(1))
+ .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
public void testMessageTransaction() throws RemoteException {
IContextHubEndpoint endpoint = registerExampleEndpoint();
testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 8c9b9bd03b9f..159b3fd7b5c3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,6 +24,7 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.app.Flags.FLAG_NM_SUMMARIZATION;
import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
@@ -105,6 +106,7 @@ import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
@@ -18308,9 +18310,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Post some notifications and classify in different bundles
final int numNotifications = NotificationChannel.SYSTEM_RESERVED_IDS.size();
final int numNewsNotifications = 1;
+ List<String> postedNotificationKeys = new ArrayList();
for (int i = 0; i < numNotifications; i++) {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, mUserId);
mService.addNotification(r);
+ postedNotificationKeys.add(r.getKey());
Bundle signals = new Bundle();
final int adjustmentType = i + 1;
signals.putInt(Adjustment.KEY_TYPE, adjustmentType);
@@ -18330,7 +18334,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
//Check that all notifications classified as TYPE_NEWS have been unbundled
- for (NotificationRecord record : mService.mNotificationList) {
+ for (String key : postedNotificationKeys) {
+ NotificationRecord record= mService.mNotificationsByKey.get(key);
// Check that the original channel was restored
// for notifications classified as TYPE_NEWS
if (record.getBundleType() == TYPE_NEWS) {
@@ -18355,7 +18360,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Check that the bundle channel was restored
verify(mRankingHandler, times(numNewsNotifications)).requestSort();
- for (NotificationRecord record : mService.mNotificationList) {
+ for (String key : postedNotificationKeys) {
+ NotificationRecord record= mService.mNotificationsByKey.get(key);
assertThat(record.getChannel().getId()).isIn(NotificationChannel.SYSTEM_RESERVED_IDS);
}
}
@@ -18425,6 +18431,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NM_SUMMARIZATION})
+ public void testDisableBundleAdjustmentByPkg_unsummarizesNotifications() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isAdjustmentAllowedForPackage(anyString(), anyString())).thenReturn(true);
+
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, mUserId);
+ mService.addNotification(r);
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, "hello");
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals,
+ "", r.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ Mockito.clearInvocations(mRankingHandler);
+
+ // Disable summarization for package
+ mBinderService.setAdjustmentSupportedForPackage(KEY_SUMMARIZATION, mPkg, false);
+ verify(mRankingHandler).requestSort();
+ mService.handleRankingSort();
+
+ assertThat(mService.mNotificationsByKey.get(r.getKey()).getSummarization()).isNull();
+ }
+
+ @Test
@EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
FLAG_NOTIFICATION_FORCE_GROUPING,
FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
@@ -18627,6 +18663,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NM_SUMMARIZATION})
+ public void testDisableBundleAdjustment_unsummarizesNotifications() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isAdjustmentAllowedForPackage(anyString(), anyString())).thenReturn(true);
+
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, mUserId);
+ mService.addNotification(r);
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, "hello");
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals,
+ "", r.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ Mockito.clearInvocations(mRankingHandler);
+
+ // Disable summarization for package
+ mBinderService.disallowAssistantAdjustment(KEY_SUMMARIZATION);
+ verify(mRankingHandler).requestSort();
+ mService.handleRankingSort();
+
+ assertThat(mService.mNotificationsByKey.get(r.getKey()).getSummarization()).isNull();
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void clearAll_fromUser_willSendDeleteIntentForCachedSummaries() throws Exception {
NotificationRecord n = generateNotificationRecord(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 1cb1e3cae413..ec264034871a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -25,6 +25,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
@@ -67,6 +68,7 @@ import android.os.LocaleList;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayInfo;
@@ -75,6 +77,8 @@ import android.view.WindowManager;
import androidx.test.filters.MediumTest;
+import com.android.server.UiThread;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -164,11 +168,13 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any());
}
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
@Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
final ArrayList<Integer> removed = new ArrayList<>();
+ final ArrayList<Integer> desktopModeEligibleChanged = new ArrayList<>();
IDisplayWindowListener listener = new IDisplayWindowListener.Stub() {
@Override
public void onDisplayAdded(int displayId) {
@@ -194,6 +200,11 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
@Override
public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
List<Rect> unrestricted) {}
+
+ @Override
+ public void onDesktopModeEligibleChanged(int displayId) {
+ desktopModeEligibleChanged.add(displayId);
+ }
};
int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
for (int i = 0; i < displayIds.length; i++) {
@@ -218,7 +229,25 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
assertEquals(1, changed.size());
assertEquals(0, removed.size());
changed.clear();
+
+ // Check adding decoration
+ doReturn(true).when(newDisp1).allowContentModeSwitch();
+ doReturn(true).when(newDisp1).isSystemDecorationsSupported();
+ mAtm.mWindowManager.setShouldShowSystemDecors(newDisp1.mDisplayId, true);
+ waitHandlerIdle(UiThread.getHandler());
+ assertEquals(1, desktopModeEligibleChanged.size());
+ assertEquals(newDisp1.mDisplayId, (int) desktopModeEligibleChanged.get(0));
+ desktopModeEligibleChanged.clear();
+ // Check removing decoration
+ doReturn(false).when(newDisp1).isSystemDecorationsSupported();
+ mAtm.mWindowManager.setShouldShowSystemDecors(newDisp1.mDisplayId, false);
+ waitHandlerIdle(UiThread.getHandler());
+ assertEquals(1, desktopModeEligibleChanged.size());
+ assertEquals(newDisp1.mDisplayId, (int) desktopModeEligibleChanged.get(0));
+ desktopModeEligibleChanged.clear();
+
// Check that removal is reported
+ changed.clear();
newDisp1.remove();
assertEquals(0, added.size());
assertEquals(0, changed.size());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index f587d6e8c346..678230564b25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,7 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
@@ -282,6 +282,7 @@ public class DesktopModeLaunchParamsModifierTests extends
final DisplayContent dc = spy(createNewDisplay());
final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build();
+ existingFreeformTask.topRunningActivity().launchMode = LAUNCH_SINGLE_INSTANCE;
existingFreeformTask.setBounds(
/* left */ 0,
/* top */ 0,
@@ -293,8 +294,8 @@ public class DesktopModeLaunchParamsModifierTests extends
// so first instance will close.
final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName)
.setCreateActivity(true).build();
+ launchingTask.topRunningActivity().launchMode = LAUNCH_SINGLE_INSTANCE;
launchingTask.onDisplayChanged(dc);
- launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
// New instance should inherit task bounds of old instance.
assertEquals(RESULT_DONE,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 5624677779a2..fa77e42611e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -562,26 +562,6 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
- public void testDeferredRemovalByAnimating() {
- final WindowState appWindow = newWindowBuilder("appWindow", TYPE_APPLICATION).build();
- makeWindowVisible(appWindow);
- spyOn(appWindow.mWinAnimator);
- doReturn(true).when(appWindow.mWinAnimator).getShown();
- final AnimationAdapter animation = mock(AnimationAdapter.class);
- final ActivityRecord activity = appWindow.mActivityRecord;
- activity.startAnimation(appWindow.getPendingTransaction(),
- animation, false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION);
-
- appWindow.removeIfPossible();
- assertTrue(appWindow.mAnimatingExit);
- assertFalse(appWindow.mRemoved);
-
- activity.cancelAnimation();
- assertFalse(appWindow.mAnimatingExit);
- assertTrue(appWindow.mRemoved);
- }
-
- @Test
public void testOnExitAnimationDone() {
final WindowState parent = newWindowBuilder("parent", TYPE_APPLICATION).build();
final WindowState child = newWindowBuilder("child", TYPE_APPLICATION_PANEL).setParent(
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d7f80a94081a..2095ee83b77d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3135,6 +3135,16 @@ interface ITelephony {
boolean setSatelliteIgnoreCellularServiceState(in boolean enabled);
/**
+ * This API can be used by only CTS to control the feature
+ * {@code config_support_disable_satellite_while_enable_in_progress}.
+ *
+ * @param reset Whether to reset the override.
+ * @param supported Whether to support the feature.
+ * @return {@code true} if the value is set successfully, {@code false} otherwise.
+ */
+ boolean setSupportDisableSatelliteWhileEnableInProgress(boolean reset, boolean supported);
+
+ /**
* This API can be used by only CTS to update satellite pointing UI app package and class names.
*
* @param packageName The package name of the satellite pointing UI app.
diff --git a/vendor/google_testing/integration/tests/scenarios/screenshots/cuttlefish/flexiglass/six_digits_pin_delete.png b/vendor/google_testing/integration/tests/scenarios/screenshots/cuttlefish/flexiglass/six_digits_pin_delete.png
new file mode 100644
index 000000000000..d4e72fbd6e2f
--- /dev/null
+++ b/vendor/google_testing/integration/tests/scenarios/screenshots/cuttlefish/flexiglass/six_digits_pin_delete.png
Binary files differ