summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig11
-rw-r--r--core/java/android/widget/RemoteViews.java9
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/res/values/config_display.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt1
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java23
-rw-r--r--packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt10
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt45
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt66
-rw-r--r--packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt17
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt50
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt9
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt72
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java113
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java51
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java38
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java44
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java18
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java43
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java72
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java105
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt194
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt)70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilterTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt)26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt78
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_shelf.xml4
-rw-r--r--packages/SystemUI/res/values/styles.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfBackgroundView.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfIconContainer.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizer.kt (renamed from packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt)23
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt (renamed from packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt)38
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java27
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java36
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java72
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java3
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java9
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java58
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java26
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java49
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java30
-rw-r--r--services/core/java/com/android/server/wm/LockTaskController.java9
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java37
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java10
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java38
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java64
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java42
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java92
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java47
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java151
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java24
141 files changed, 2659 insertions, 1106 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c57243d0bc73..2ad6669979b7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9410,6 +9410,13 @@ public final class Settings {
"even_dimmer_min_nits";
/**
+ * Setting that holds EM_VALUE (proprietary)
+ *
+ * @hide
+ */
+ public static final String EM_VALUE =
+ "em_value";
+ /**
* List of the enabled print services.
*
* N and beyond uses {@link #DISABLED_PRINT_SERVICES}. But this might be used in an upgrade
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 41567fbf8b51..4258ca4fde01 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -194,3 +194,14 @@ flag {
is_fixed_read_only: true
is_exported: true
}
+
+flag {
+ name: "fallback_display_for_secondary_user_on_secondary_display"
+ namespace: "input_method"
+ description: "Feature flag to fix the fallback display bug for visible background users"
+ bug: "383228193"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9fe3fd6ddc1a..7c75d7b30037 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5820,8 +5820,13 @@ public class RemoteViews implements Parcelable, Filter {
mActions.forEach(action -> {
if (viewId == action.mViewId
&& action instanceof SetOnClickResponse setOnClickResponse) {
- setOnClickResponse.mResponse.handleViewInteraction(
- player, params.handler);
+ final RemoteResponse response = setOnClickResponse.mResponse;
+ if (response.mFillIntent == null) {
+ response.mFillIntent = new Intent();
+ }
+ response.mFillIntent.putExtra(
+ "remotecompose_metadata", metadata);
+ response.handleViewInteraction(player, params.handler);
}
});
});
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index c901ee1e3f8f..cf81ba157bf9 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -106,6 +106,7 @@ message SecureSettingsProto {
optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto hct_rect_prompt_status = 60 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/values/config_display.xml b/core/res/res/values/config_display.xml
index c458d0e9a3c0..b6500b71e8a9 100644
--- a/core/res/res/values/config_display.xml
+++ b/core/res/res/values/config_display.xml
@@ -31,5 +31,8 @@
<bool name="config_evenDimmerEnabled">false</bool>
<!-- Jar file path to look for PluginProvider -->
<string name="config_pluginsProviderJarPath"/>
-
+ <!-- Indicate available EM_VALUE options -->
+ <integer-array name="config_availableEMValueOptions">
+ <item>0</item> <!-- DEFAULT -->
+ </integer-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ee2839788af..4789624685c2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1309,6 +1309,7 @@
<java-symbol type="array" name="config_securityStatePackages" />
<java-symbol type="array" name="stoppable_fgs_system_apps" />
<java-symbol type="array" name="vendor_stoppable_fgs_system_apps" />
+ <java-symbol type="array" name="config_availableEMValueOptions" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="default_lock_wallpaper" />
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 348f13a493b1..8efeecb56dbf 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
@@ -1449,6 +1449,15 @@ public class BubbleController implements ConfigurationChangeListener,
}
/**
+ * Expands and selects a bubble created from a running task in a different mode.
+ *
+ * @param taskInfo the task.
+ */
+ public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(384976265): Not implemented yet
+ }
+
+ /**
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
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 d8c7f4cbb698..48b0a6cb364b 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
@@ -736,7 +736,8 @@ public abstract class WMShellModule {
DesktopModeEventLogger desktopModeEventLogger,
DesktopModeUiEventLogger desktopModeUiEventLogger,
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
- DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider) {
+ DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ Optional<BubbleController> bubbleController) {
return new DesktopTasksController(
context,
shellInit,
@@ -768,7 +769,8 @@ public abstract class WMShellModule {
desktopModeEventLogger,
desktopModeUiEventLogger,
desktopTilingDecorViewModel,
- desktopWallpaperActivityTokenProvider);
+ desktopWallpaperActivityTokenProvider,
+ bubbleController);
}
@WMSingleton
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 4e7cba23116c..d180ea7b79ff 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
@@ -73,6 +73,7 @@ import com.android.wm.shell.Flags.enableFlexibleSplit
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ExternalInterfaceBinder
@@ -172,6 +173,7 @@ class DesktopTasksController(
private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val bubbleController: Optional<BubbleController>,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -2190,6 +2192,19 @@ class DesktopTasksController(
}
}
+ /** Requests a task be transitioned from whatever mode it's in to a bubble. */
+ fun requestFloat(taskInfo: RunningTaskInfo) {
+ val isDragging = dragToDesktopTransitionHandler.inProgress
+ val shouldRequestFloat =
+ taskInfo.isFullscreen || taskInfo.isFreeform || isDragging || taskInfo.isMultiWindow
+ if (!shouldRequestFloat) return
+ if (isDragging) {
+ releaseVisualIndicator()
+ } else {
+ bubbleController.ifPresent { it.expandStackAndSelectBubble(taskInfo) }
+ }
+ }
+
private fun getDefaultDensityDpi(): Int {
return context.resources.displayMetrics.densityDpi
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
index f6ebf7221e82..6e12bf4d1dda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
@@ -90,10 +90,10 @@ constructor(
Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
Source.KEYBOARD_SHORTCUT -> null
Source.HEADER_DRAG_TO_TOP -> null
- Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null
- Source.MAXIMIZE_MENU_TO_RESTORE -> null
- Source.DOUBLE_TAP_TO_MAXIMIZE -> null
- Source.DOUBLE_TAP_TO_RESTORE -> null
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW
+ Source.MAXIMIZE_MENU_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
+ Source.DOUBLE_TAP_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW
+ Source.DOUBLE_TAP_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
}
/** The direction to which the task is being resized. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index fd387d1811fb..37296531ee34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -350,7 +350,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
- mPipScheduler.removePipAfterAnimation();
+ mPipScheduler.scheduleRemovePip();
}
/** Sets the movement bounds to use to constrain PIP position animations. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 4461a5c6a70c..7f673d2efc68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -122,34 +122,26 @@ public class PipScheduler {
* Schedules exit PiP via expand transition.
*/
public void scheduleExitPipViaExpand() {
- WindowContainerTransaction wct = getExitPipViaExpandTransaction();
- if (wct != null) {
- mMainExecutor.execute(() -> {
+ mMainExecutor.execute(() -> {
+ if (!mPipTransitionState.isInPip()) return;
+ WindowContainerTransaction wct = getExitPipViaExpandTransaction();
+ if (wct != null) {
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
null /* destinationBounds */);
- });
- }
- }
-
- // TODO: Optimize this by running the animation as part of the transition
- /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */
- public void removePipAfterAnimation() {
- SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext,
- mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
- animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
- animator.start();
+ }
+ });
}
/** Schedules remove PiP transition. */
- private void scheduleRemovePipImmediately() {
- WindowContainerTransaction wct = getRemovePipTransaction();
- if (wct != null) {
- mMainExecutor.execute(() -> {
+ public void scheduleRemovePip() {
+ mMainExecutor.execute(() -> {
+ if (!mPipTransitionState.isInPip()) return;
+ WindowContainerTransaction wct = getRemovePipTransaction();
+ if (wct != null) {
mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
null /* destinationBounds */);
- });
- }
+ }
+ });
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index acb5622b041c..2e38449d4584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -278,7 +278,8 @@ public class PipTransition extends PipTransitionController implements
}
if (isRemovePipTransition(info)) {
- return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ return startRemoveAnimation(info, startTransaction, finishTransaction, finishCallback);
}
return false;
}
@@ -668,13 +669,18 @@ public class PipTransition extends PipTransitionController implements
return true;
}
- private boolean removePipImmediately(@NonNull TransitionInfo info,
+ private boolean startRemoveAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+ TransitionInfo.Change pipChange = getChangeByToken(info,
+ mPipTransitionState.getPipTaskToken());
+ mFinishCallback = finishCallback;
+ PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
+ startTransaction, PipAlphaAnimator.FADE_OUT);
+ finishTransaction.setAlpha(pipChange.getLeash(), 0f);
+ animator.setAnimationEndCallback(this::finishTransition);
+ animator.start();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index e80016d07f15..1689bb5778ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -329,7 +329,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
- boolean isDisplayRotationAnimationStarted = false;
+ int animatingDisplayId = Integer.MIN_VALUE;
final boolean isDreamTransition = isDreamTransition(info);
final boolean isOnlyTranslucent = isOnlyTranslucent(info);
final boolean isActivityLevel = isActivityLevelOnly(info);
@@ -361,7 +361,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0;
startRotationAnimation(startTransaction, change, info, anim, flags,
animations, onAnimFinish);
- isDisplayRotationAnimationStarted = true;
+ animatingDisplayId = change.getEndDisplayId();
continue;
}
} else {
@@ -426,8 +426,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Hide the invisible surface directly without animating it if there is a display
// rotation animation playing.
- if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) {
- startTransaction.hide(change.getLeash());
+ if (animatingDisplayId == change.getEndDisplayId()) {
+ if (TransitionUtil.isClosingType(mode)) {
+ startTransaction.hide(change.getLeash());
+ }
+ // Only need to play display level animation.
continue;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index df81821ff13f..edb2e1cbcca6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -546,8 +546,13 @@ public class Transitions implements RemoteCallable<Transitions>,
// When the window is moved to front, make sure the crop is updated to prevent it
// from using the old crop.
t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
- t.setWindowCrop(leash, change.getEndAbsBounds().width(),
- change.getEndAbsBounds().height());
+ if (change.getContainer() != null) {
+ // We don't want to crop on non-remotable (activity), because it can have
+ // letterbox child surface that is position at a negative position related to
+ // the activity's surface.
+ t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+ }
}
// Don't move anything that isn't independent within its parents
@@ -557,8 +562,13 @@ public class Transitions implements RemoteCallable<Transitions>,
t.setMatrix(leash, 1, 0, 0, 1);
t.setAlpha(leash, 1.f);
t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
- t.setWindowCrop(leash, change.getEndAbsBounds().width(),
- change.getEndAbsBounds().height());
+ if (change.getContainer() != null) {
+ // We don't want to crop on non-remotable (activity), because it can have
+ // letterbox child surface that is position at a negative position related
+ // to the activity's surface.
+ t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+ }
}
continue;
}
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 a7a5f09c88f8..046cb202fb11 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
@@ -776,6 +776,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN);
}
+ private void onToFloat(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ // When the app enters float, the handle will no longer be visible, meaning
+ // we shouldn't receive input for it any longer.
+ decoration.disposeStatusBarInputLayer();
+ mDesktopTasksController.requestFloat(decoration.mTaskInfo);
+ }
+
private void onNewWindow(int taskId) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
@@ -1731,6 +1743,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
onToSplitScreen(taskInfo.taskId);
return Unit.INSTANCE;
});
+ windowDecoration.setOnToFloatClickListener(() -> {
+ onToFloat(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
windowDecoration.setOpenInBrowserClickListener((intent) -> {
onOpenInBrowser(taskInfo.taskId, intent);
});
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 0d1960ad6e29..4ac89546c9c7 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
@@ -155,6 +155,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Consumer<DesktopModeTransitionSource> mOnToDesktopClickListener;
private Function0<Unit> mOnToFullscreenClickListener;
private Function0<Unit> mOnToSplitscreenClickListener;
+ private Function0<Unit> mOnToFloatClickListener;
private Function0<Unit> mOnNewWindowClickListener;
private Function0<Unit> mOnManageWindowsClickListener;
private Function0<Unit> mOnChangeAspectRatioClickListener;
@@ -351,6 +352,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnToSplitscreenClickListener = listener;
}
+ /** Registers a listener to be called when the decoration's to-split action is triggered. */
+ void setOnToFloatClickListener(Function0<Unit> listener) {
+ mOnToFloatClickListener = listener;
+ }
+
/**
* Adds a drag resize observer that gets notified on the task being drag resized.
*
@@ -1372,6 +1378,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
},
/* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
/* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
+ /* onToFloatClickListener= */ mOnToFloatClickListener,
/* onNewWindowClickListener= */ mOnNewWindowClickListener,
/* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
/* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index bb19a2cc2ad4..9d73950abcf0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -144,6 +144,7 @@ class HandleMenu(
onToDesktopClickListener: () -> Unit,
onToFullscreenClickListener: () -> Unit,
onToSplitScreenClickListener: () -> Unit,
+ onToFloatClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
@@ -162,6 +163,7 @@ class HandleMenu(
onToDesktopClickListener = onToDesktopClickListener,
onToFullscreenClickListener = onToFullscreenClickListener,
onToSplitScreenClickListener = onToSplitScreenClickListener,
+ onToFloatClickListener = onToFloatClickListener,
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
@@ -183,6 +185,7 @@ class HandleMenu(
onToDesktopClickListener: () -> Unit,
onToFullscreenClickListener: () -> Unit,
onToSplitScreenClickListener: () -> Unit,
+ onToFloatClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
@@ -208,6 +211,7 @@ class HandleMenu(
this.onToDesktopClickListener = onToDesktopClickListener
this.onToFullscreenClickListener = onToFullscreenClickListener
this.onToSplitScreenClickListener = onToSplitScreenClickListener
+ this.onToFloatClickListener = onToFloatClickListener
this.onNewWindowClickListener = onNewWindowClickListener
this.onManageWindowsClickListener = onManageWindowsClickListener
this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
@@ -502,6 +506,7 @@ class HandleMenu(
var onToDesktopClickListener: (() -> Unit)? = null
var onToFullscreenClickListener: (() -> Unit)? = null
var onToSplitScreenClickListener: (() -> Unit)? = null
+ var onToFloatClickListener: (() -> Unit)? = null
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
var onChangeAspectRatioClickListener: (() -> Unit)? = null
@@ -515,6 +520,7 @@ class HandleMenu(
splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
openInAppOrBrowserBtn.setOnClickListener { onOpenInAppOrBrowserClickListener?.invoke() }
+ floatingBtn.setOnClickListener { onToFloatClickListener?.invoke() }
openByDefaultBtn.setOnClickListener {
onOpenByDefaultClickListener?.invoke()
}
@@ -640,8 +646,9 @@ class HandleMenu(
private fun bindWindowingPill(style: MenuStyle) {
windowingPill.background.setTint(style.backgroundColor)
- // TODO: Remove once implemented.
- floatingBtn.visibility = View.GONE
+ if (!com.android.wm.shell.Flags.enableBubbleAnything()) {
+ floatingBtn.visibility = View.GONE
+ }
fullscreenBtn.isSelected = taskInfo.isFullscreen
fullscreenBtn.isEnabled = !taskInfo.isFullscreen
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 fe0852689ee9..e032616e7d43 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
@@ -93,6 +93,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
@@ -232,6 +233,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock private lateinit var mockToast: Toast
private lateinit var mockitoSession: StaticMockitoSession
@Mock private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel
+ @Mock private lateinit var bubbleController: BubbleController
@Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration
@Mock private lateinit var resources: Resources
@Mock
@@ -383,6 +385,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeUiEventLogger,
desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
+ Optional.of(bubbleController),
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index 3fe8c109807a..a8aa25700c7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -120,15 +120,22 @@ public class PipSchedulerTest {
@Test
public void scheduleExitPipViaExpand_nullTaskToken_noop() {
setNullPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
- verify(mMockMainExecutor, never()).execute(any());
+ verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
+ assertNotNull(mRunnableArgumentCaptor.getValue());
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mMockPipTransitionController, never())
+ .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
}
@Test
public void scheduleExitPipViaExpand_exitTransitionCalled() {
setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
@@ -142,20 +149,13 @@ public class PipSchedulerTest {
@Test
public void removePipAfterAnimation() {
- //TODO: Update once this is changed to run animation as part of transition
setMockPipTaskToken();
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
- mPipScheduler.removePipAfterAnimation();
- verify(mMockAlphaAnimator, times(1))
- .setAnimationEndCallback(mRunnableArgumentCaptor.capture());
- assertNotNull(mRunnableArgumentCaptor.getValue());
- verify(mMockAlphaAnimator, times(1)).start();
-
- mRunnableArgumentCaptor.getValue().run();
+ mPipScheduler.scheduleRemovePip();
verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
assertNotNull(mRunnableArgumentCaptor.getValue());
-
mRunnableArgumentCaptor.getValue().run();
verify(mMockPipTransitionController, times(1))
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 e99e5cce8b27..7dac0859b7e9 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
@@ -1091,6 +1091,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1127,6 +1128,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1158,6 +1160,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1226,6 +1229,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any(),
anyBoolean()
@@ -1258,6 +1262,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
/* forceShowSystemBars= */ eq(true)
);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cbfb57edc72d..f90988e90b22 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -307,6 +307,7 @@ class HandleMenuTest : ShellTestCase() {
onToDesktopClickListener = mock(),
onToFullscreenClickListener = mock(),
onToSplitScreenClickListener = mock(),
+ onToFloatClickListener = mock(),
onNewWindowClickListener = mock(),
onManageWindowsClickListener = mock(),
onChangeAspectRatioClickListener = mock(),
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 170cb4546d0c..551f52301b56 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -47,19 +47,19 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
+
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
import com.android.packageinstaller.handheld.ErrorDialogFragment;
import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
import com.android.packageinstaller.television.ErrorFragment;
import com.android.packageinstaller.television.UninstallAlertFragment;
import com.android.packageinstaller.television.UninstallAppProgress;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
import com.android.packageinstaller.v2.ui.UninstallLaunch;
-import java.util.List;
-
/*
* This activity presents UI to uninstall an application. Usually launched with intent
* Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute
@@ -181,12 +181,15 @@ public class UninstallerActivity extends Activity {
if (mDialogInfo.user == null) {
mDialogInfo.user = Process.myUserHandle();
} else {
- List<UserHandle> profiles = userManager.getUserProfiles();
- if (!profiles.contains(mDialogInfo.user)) {
- Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
- + "for user " + mDialogInfo.user);
- showUserIsNotAllowed();
- return;
+ if (mDialogInfo.user != Process.myUserHandle()) {
+ final boolean isCurrentUserProfileOwner =
+ (Process.myUserHandle() == userManager.getProfileParent(mDialogInfo.user));
+ if (!isCurrentUserProfileOwner) {
+ Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+ + "for user " + mDialogInfo.user);
+ showUserIsNotAllowed();
+ return;
+ }
}
}
diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
index 620d717faf69..7432254b57a4 100644
--- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
+++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
@@ -129,7 +129,15 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() {
)
}
}
- processingEnv.filer.createSourceFile("$outputPkg.$outputClass").openWriter().use {
+ val javaFileObject =
+ try {
+ processingEnv.filer.createSourceFile("$outputPkg.$outputClass")
+ } catch (e: Exception) {
+ // quick fix: gradle runs this processor twice unexpectedly
+ warn("cannot createSourceFile: $e")
+ return
+ }
+ javaFileObject.openWriter().use {
it.write("package $outputPkg;\n\n")
it.write("import $PACKAGE.$PREFERENCE_SCREEN_METADATA;\n\n")
it.write("// Generated by annotation processor for @$ANNOTATION_NAME\n")
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index c1edbdc20361..1e70a32cb38b 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -22,7 +22,6 @@ import android.os.Bundle
import androidx.annotation.AnyThread
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
-import androidx.fragment.app.Fragment
/**
* Interface provides preference metadata (title, summary, icon, etc.).
@@ -170,47 +169,3 @@ interface PreferenceMetadata {
/** Metadata of preference category. */
@AnyThread
open class PreferenceCategory(override val key: String, override val title: Int) : PreferenceGroup
-
-/** Metadata of preference screen. */
-@AnyThread
-interface PreferenceScreenMetadata : PreferenceMetadata {
-
- /**
- * The screen title resource, which precedes [getScreenTitle] if provided.
- *
- * By default, screen title is same with [title].
- */
- val screenTitle: Int
- get() = title
-
- /** Returns dynamic screen title, use [screenTitle] whenever possible. */
- fun getScreenTitle(context: Context): CharSequence? = null
-
- /** Returns the fragment class to show the preference screen. */
- fun fragmentClass(): Class<out Fragment>?
-
- /**
- * Indicates if [getPreferenceHierarchy] returns a complete hierarchy of the preference screen.
- *
- * If `true`, the result of [getPreferenceHierarchy] will be used to inflate preference screen.
- * Otherwise, it is an intermediate state called hybrid mode, preference hierarchy is
- * represented by other ways (e.g. XML resource) and [PreferenceMetadata]s in
- * [getPreferenceHierarchy] will only be used to bind UI widgets.
- */
- fun hasCompleteHierarchy(): Boolean = true
-
- /**
- * Returns the hierarchy of preference screen.
- *
- * The implementation MUST include all preferences into the hierarchy regardless of the runtime
- * conditions. DO NOT check any condition (except compile time flag) before adding a preference.
- */
- fun getPreferenceHierarchy(context: Context): PreferenceHierarchy
-
- /**
- * Returns the [Intent] to show current preference screen.
- *
- * @param metadata the preference to locate when show the screen
- */
- fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? = null
-}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt
new file mode 100644
index 000000000000..fc68ea7f8a2f
--- /dev/null
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.settingslib.metadata
+
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.AnyThread
+import androidx.fragment.app.Fragment
+
+/** Metadata of preference screen. */
+@AnyThread
+interface PreferenceScreenMetadata : PreferenceMetadata {
+
+ /**
+ * The screen title resource, which precedes [getScreenTitle] if provided.
+ *
+ * By default, screen title is same with [title].
+ */
+ val screenTitle: Int
+ get() = title
+
+ /** Returns dynamic screen title, use [screenTitle] whenever possible. */
+ fun getScreenTitle(context: Context): CharSequence? = null
+
+ /** Returns the fragment class to show the preference screen. */
+ fun fragmentClass(): Class<out Fragment>?
+
+ /**
+ * Indicates if [getPreferenceHierarchy] returns a complete hierarchy of the preference screen.
+ *
+ * If `true`, the result of [getPreferenceHierarchy] will be used to inflate preference screen.
+ * Otherwise, it is an intermediate state called hybrid mode, preference hierarchy is
+ * represented by other ways (e.g. XML resource) and [PreferenceMetadata]s in
+ * [getPreferenceHierarchy] will only be used to bind UI widgets.
+ */
+ fun hasCompleteHierarchy(): Boolean = true
+
+ /**
+ * Returns the hierarchy of preference screen.
+ *
+ * The implementation MUST include all preferences into the hierarchy regardless of the runtime
+ * conditions. DO NOT check any condition (except compile time flag) before adding a preference.
+ */
+ fun getPreferenceHierarchy(context: Context): PreferenceHierarchy
+
+ /**
+ * Returns the [Intent] to show current preference screen.
+ *
+ * @param metadata the preference to locate when show the screen
+ */
+ fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? = null
+}
diff --git a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
index 1815d040bb18..4315238ad7c1 100644
--- a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
+++ b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
@@ -268,7 +268,9 @@ public class SliderPreference extends Preference {
mSlider.setValueFrom(mMin);
mSlider.setValueTo(mMax);
mSlider.setValue(mSliderValue);
+ mSlider.clearOnSliderTouchListeners();
mSlider.addOnSliderTouchListener(mTouchListener);
+ mSlider.clearOnChangeListeners();
mSlider.addOnChangeListener(mChangeListener);
mSlider.setEnabled(isEnabled());
@@ -487,7 +489,7 @@ public class SliderPreference extends Preference {
* set the {@link Slider}'s value to the stored value.
*/
void syncValueInternal(@NonNull Slider slider) {
- int sliderValue = mMin + (int) slider.getValue();
+ int sliderValue = (int) slider.getValue();
if (sliderValue != mSliderValue) {
if (callChangeListener(sliderValue)) {
setValueInternal(sliderValue, false);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index fcaedd2b80bd..a52222ba678b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -64,7 +64,7 @@ object SettingsDimension {
bottom = itemPaddingVertical,
)
val itemPaddingAround = 8.dp
- val itemDividerHeight = 32.dp
+ val itemDividerHeight = if (isSpaExpressiveEnabled) 40.dp else 32.dp
val iconLarge = 48.dp
val introIconSize = 40.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 3aa7ad0a992d..8b1ed939a967 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -28,8 +28,6 @@ import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -104,8 +102,17 @@ internal fun TwoTargetPreference(
@Composable
private fun PreferenceDivider() {
Box(
- Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd)
- .size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
- .background(color = MaterialTheme.colorScheme.divider)
+ if (isSpaExpressiveEnabled) {
+ Modifier.padding(
+ start = SettingsDimension.paddingSmall,
+ end = SettingsDimension.paddingExtraSmall6,
+ )
+ .size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
+ .background(color = MaterialTheme.colorScheme.outline)
+ } else {
+ Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd)
+ .size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
+ .background(color = MaterialTheme.colorScheme.divider)
+ }
)
}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
index 99c6a3fd3465..591dff7af5a0 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
@@ -17,18 +17,50 @@
package com.android.settingslib.spa.testutils
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.withTimeoutOrNull
+/**
+ * Collects the first element emitted by this flow within a given timeout.
+ *
+ * If the flow emits a value within the given timeout, this function returns that value. If the
+ * timeout expires before the flow emits any values, this function returns null.
+ *
+ * This function is similar to [kotlinx.coroutines.flow.firstOrNull], but it adds a timeout to
+ * prevent potentially infinite waiting.
+ *
+ * @param timeMillis The timeout in milliseconds. Defaults to 500 milliseconds.
+ * @return The first element emitted by the flow within the timeout, or null if the timeout expires.
+ */
suspend fun <T> Flow<T>.firstWithTimeoutOrNull(timeMillis: Long = 500): T? =
- withTimeoutOrNull(timeMillis) {
- first()
- }
+ withTimeoutOrNull(timeMillis) { firstOrNull() }
-suspend fun <T> Flow<T>.toListWithTimeout(timeMillis: Long = 500): List<T> {
- val list = mutableListOf<T>()
- return withTimeoutOrNull(timeMillis) {
- toList(list)
- } ?: list
+/**
+ * Collects elements from this flow for a given time and returns the last emitted element, or null
+ * if the flow did not emit any elements.
+ *
+ * This function is useful when you need to retrieve the last value emitted by a flow within a
+ * specific timeframe, but the flow might complete without emitting anything or might not emit a
+ * value within the given timeout.
+ *
+ * @param timeMillis The timeout in milliseconds. Defaults to 500ms.
+ * @return The last emitted element, or null if the flow did not emit any elements.
+ */
+suspend fun <T> Flow<T>.lastWithTimeoutOrNull(timeMillis: Long = 500): T? =
+ toListWithTimeout(timeMillis).lastOrNull()
+
+/**
+ * Collects elements from this flow into a list with a timeout.
+ *
+ * This function attempts to collect all elements from the flow and store them in a list. If the
+ * collection process takes longer than the specified timeout, the collection is canceled and the
+ * function returns the elements collected up to that point.
+ *
+ * @param timeMillis The timeout duration in milliseconds. Defaults to 500 milliseconds.
+ * @return A list containing the collected elements, or an empty list if the timeout was reached
+ * before any elements were collected.
+ */
+suspend fun <T> Flow<T>.toListWithTimeout(timeMillis: Long = 500): List<T> = buildList {
+ withTimeoutOrNull(timeMillis) { toList(this@buildList) }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index 7d6ee1928111..60ef73317316 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -21,9 +21,8 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
/**
@@ -35,6 +34,8 @@ fun DisposableBroadcastReceiverAsUser(
userHandle: UserHandle,
onReceive: (Intent) -> Unit,
) {
- LocalContext.current.broadcastReceiverAsUserFlow(intentFilter, userHandle)
- .collectLatestWithLifecycle(LocalLifecycleOwner.current, action = onReceive)
+ val context = LocalContext.current
+ LaunchedEffect(Unit) {
+ context.broadcastReceiverAsUserFlow(intentFilter, userHandle).collect(onReceive)
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
index a59a724d9346..4221f9fb5111 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -22,10 +22,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
@@ -38,34 +39,39 @@ import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
class DisposableBroadcastReceiverAsUserTest {
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
private var registeredBroadcastReceiver: BroadcastReceiver? = null
- private val context = mock<Context> {
- on {
- registerReceiverAsUser(
- any(),
- eq(USER_HANDLE),
- eq(INTENT_FILTER),
- isNull(),
- isNull(),
- eq(Context.RECEIVER_NOT_EXPORTED),
- )
- } doAnswer {
- registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
- null
+ private val context =
+ mock<Context> {
+ on {
+ registerReceiverAsUser(
+ any(),
+ eq(USER_HANDLE),
+ eq(INTENT_FILTER),
+ isNull(),
+ isNull(),
+ eq(Context.RECEIVER_NOT_EXPORTED),
+ )
+ } doAnswer
+ {
+ registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+ null
+ }
+
+ on { unregisterReceiver(any()) } doAnswer
+ {
+ if (registeredBroadcastReceiver === it.arguments[0]) {
+ registeredBroadcastReceiver = null
+ }
+ }
}
- }
@Test
fun broadcastReceiver_registered() {
composeTestRule.setContent {
- CompositionLocalProvider(
- LocalContext provides context,
- LocalLifecycleOwner provides TestLifecycleOwner(),
- ) {
+ CompositionLocalProvider(LocalContext provides context) {
DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {}
}
}
@@ -77,10 +83,7 @@ class DisposableBroadcastReceiverAsUserTest {
fun broadcastReceiver_isCalledOnReceive() {
var onReceiveIsCalled = false
composeTestRule.setContent {
- CompositionLocalProvider(
- LocalContext provides context,
- LocalLifecycleOwner provides TestLifecycleOwner(),
- ) {
+ CompositionLocalProvider(LocalContext provides context) {
DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {
onReceiveIsCalled = true
}
@@ -92,6 +95,23 @@ class DisposableBroadcastReceiverAsUserTest {
composeTestRule.waitUntil { onReceiveIsCalled }
}
+ @Test
+ fun broadcastReceiver_unregistered() {
+ var isBroadcastReceiverRegistered by mutableStateOf(true)
+ composeTestRule.setContent {
+ if (isBroadcastReceiverRegistered) {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {}
+ }
+ }
+ }
+ composeTestRule.waitUntil { registeredBroadcastReceiver != null }
+
+ isBroadcastReceiverRegistered = false
+
+ composeTestRule.waitUntil { registeredBroadcastReceiver == null }
+ }
+
private companion object {
val USER_HANDLE: UserHandle = UserHandle.of(0)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
index 31522c1209f7..7ef11eb865ba 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
@@ -23,7 +23,6 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spa.testutils.toListWithTimeout
-import com.android.settingslib.spaprivileged.framework.common.asUser
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
@@ -35,6 +34,7 @@ import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class PermissionsChangedFlowTest {
@@ -49,7 +49,7 @@ class PermissionsChangedFlowTest {
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
- on { asUser(APP.userHandle) } doReturn mock
+ doReturn(mock).whenever(mock).createContextAsUser(APP.userHandle, 0)
on { packageManager } doReturn mockPackageManager
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
index ca0cad759a08..7d91050d4289 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
@@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
+import android.media.MediaRecorder;
import androidx.annotation.IntDef;
@@ -61,15 +62,20 @@ public final class HearingAidAudioRoutingConstants {
@IntDef({
RoutingValue.AUTO,
RoutingValue.HEARING_DEVICE,
- RoutingValue.DEVICE_SPEAKER,
+ RoutingValue.BUILTIN_DEVICE,
})
public @interface RoutingValue {
int AUTO = 0;
int HEARING_DEVICE = 1;
- int DEVICE_SPEAKER = 2;
+ int BUILTIN_DEVICE = 2;
}
- public static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
+ public static final AudioDeviceAttributes BUILTIN_SPEAKER = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+ public static final AudioDeviceAttributes BUILTIN_MIC = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, "");
+
+ public static final int MICROPHONE_SOURCE_VOICE_COMMUNICATION =
+ MediaRecorder.AudioSource.VOICE_COMMUNICATION;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
index 8eaea0e3561b..1f727259db7e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -16,26 +16,34 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.BUILTIN_MIC;
+import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.MICROPHONE_SOURCE_VOICE_COMMUNICATION;
+
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
- * A helper class to configure the routing strategy for hearing aids.
+ * A helper class to configure the audio routing for hearing aids.
*/
public class HearingAidAudioRoutingHelper {
+ private static final String TAG = "HearingAidAudioRoutingHelper";
+
private final AudioManager mAudioManager;
public HearingAidAudioRoutingHelper(Context context) {
@@ -73,26 +81,26 @@ public class HearingAidAudioRoutingHelper {
* @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio
* routing
* @param routingValue one of value defined in
- * {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing
+ * {@link RoutingValue}, denotes routing
* destination.
* @return {code true} if the routing value successfully configure
*/
public boolean setPreferredDeviceRoutingStrategies(
List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice,
- @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+ @RoutingValue int routingValue) {
boolean status;
switch (routingValue) {
- case HearingAidAudioRoutingConstants.RoutingValue.AUTO:
+ case RoutingValue.AUTO:
status = removePreferredDeviceForStrategies(supportedStrategies);
return status;
- case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE:
+ case RoutingValue.HEARING_DEVICE:
status = removePreferredDeviceForStrategies(supportedStrategies);
status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice);
return status;
- case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER:
+ case RoutingValue.BUILTIN_DEVICE:
status = removePreferredDeviceForStrategies(supportedStrategies);
status &= setPreferredDeviceForStrategies(supportedStrategies,
- HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT);
+ HearingAidAudioRoutingConstants.BUILTIN_SPEAKER);
return status;
default:
throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
@@ -100,21 +108,76 @@ public class HearingAidAudioRoutingHelper {
}
/**
- * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}.
+ * Set the preferred input device for calls.
*
- * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device}
+ * <p>Note that hearing device needs to be valid input device to be found in AudioManager.
+ * <p>Routing value can be:
+ * <ul>
+ * <li> {@link RoutingValue#AUTO} - Allow the system to automatically select the appropriate
+ * audio routing for calls.</li>
+ * <li> {@link RoutingValue#HEARING_DEVICE} - Set input device to this hearing device.</li>
+ * <li> {@link RoutingValue#BUILTIN_DEVICE} - Set input device to builtin microphone. </li>
+ * </ul>
+ * @param routingValue The desired routing value for calls
+ * @return {@code true} if the operation was successful
+ */
+ public boolean setPreferredInputDeviceForCalls(@Nullable CachedBluetoothDevice hearingDevice,
+ @RoutingValue int routingValue) {
+ AudioDeviceAttributes hearingDeviceAttributes = getMatchedHearingDeviceAttributesInput(
+ hearingDevice);
+ if (hearingDeviceAttributes == null) {
+ Log.w(TAG, "Can not find expected input AudioDeviceAttributes for hearing device: "
+ + hearingDevice.getDevice().getAnonymizedAddress());
+ return false;
+ }
+
+ final int audioSource = MICROPHONE_SOURCE_VOICE_COMMUNICATION;
+ return switch (routingValue) {
+ case RoutingValue.AUTO ->
+ mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
+ case RoutingValue.HEARING_DEVICE -> {
+ mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
+ yield mAudioManager.setPreferredDeviceForCapturePreset(audioSource,
+ hearingDeviceAttributes);
+ }
+ case RoutingValue.BUILTIN_DEVICE -> {
+ mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
+ yield mAudioManager.setPreferredDeviceForCapturePreset(audioSource, BUILTIN_MIC);
+ }
+ default -> throw new IllegalArgumentException(
+ "Unexpected routingValue: " + routingValue);
+ };
+ }
+
+ /**
+ * Clears the preferred input device for calls.
+ *
+ * {@code true} if the operation was successful
+ */
+ public boolean clearPreferredInputDeviceForCalls() {
+ return mAudioManager.clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ }
+
+ /**
+ * Gets the matched output hearing device {@link AudioDeviceAttributes} for {@code device}.
+ *
+ * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} and
+ * {@link CachedBluetoothDevice#getMemberDevice()} of {@code device}
*
* @param device the {@link CachedBluetoothDevice} need to be hearing aid device
* @return the requested AudioDeviceAttributes or {@code null} if not match
*/
@Nullable
- public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) {
+ public AudioDeviceAttributes getMatchedHearingDeviceAttributesForOutput(
+ @Nullable CachedBluetoothDevice device) {
if (device == null || !device.isHearingAidDevice()) {
return null;
}
AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo audioDevice : audioDevices) {
+ //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
// ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET
if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID
|| audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
@@ -126,6 +189,35 @@ public class HearingAidAudioRoutingHelper {
return null;
}
+ /**
+ * Gets the matched input hearing device {@link AudioDeviceAttributes} for {@code device}.
+ *
+ * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} and
+ * {@link CachedBluetoothDevice#getMemberDevice()} of {@code device}
+ *
+ * @param device the {@link CachedBluetoothDevice} need to be hearing aid device
+ * @return the requested AudioDeviceAttributes or {@code null} if not match
+ */
+ @Nullable
+ private AudioDeviceAttributes getMatchedHearingDeviceAttributesInput(
+ @Nullable CachedBluetoothDevice device) {
+ if (device == null || !device.isHearingAidDevice()) {
+ return null;
+ }
+
+ AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo audioDevice : audioDevices) {
+ //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
+ // HAP for TYPE_BLE_HEADSET
+ if (audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+ if (matchAddress(device, audioDevice)) {
+ return new AudioDeviceAttributes(audioDevice);
+ }
+ }
+ }
+ return null;
+ }
+
private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) {
final String audioDeviceAddress = audioDevice.getAddress();
final CachedBluetoothDevice subDevice = device.getSubDevice();
@@ -142,7 +234,6 @@ public class HearingAidAudioRoutingHelper {
boolean status = true;
for (AudioProductStrategy strategy : strategies) {
status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);
-
}
return status;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 1ca4c2b39a70..ad34e837f508 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -31,6 +31,7 @@ import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue;
import java.util.HashSet;
import java.util.List;
@@ -277,13 +278,19 @@ public class HearingAidDeviceManager {
void onActiveDeviceChanged(CachedBluetoothDevice device) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
- if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice(
- BluetoothProfile.LE_AUDIO)) {
+ if (device.isConnectedHearingAidDevice()) {
setAudioRoutingConfig(device);
} else {
clearAudioRoutingConfig();
}
}
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ if (device.isConnectedHearingAidDevice()) {
+ setMicrophoneForCalls(device);
+ } else {
+ clearMicrophoneForCalls();
+ }
+ }
}
void syncDeviceIfNeeded(CachedBluetoothDevice device) {
@@ -311,9 +318,25 @@ public class HearingAidDeviceManager {
HearingDeviceLocalDataManager.clear(mContext, device.getDevice());
}
+ private void setMicrophoneForCalls(CachedBluetoothDevice device) {
+ boolean useRemoteMicrophone = device.getDevice().isMicrophonePreferredForCalls();
+ boolean status = mRoutingHelper.setPreferredInputDeviceForCalls(device,
+ useRemoteMicrophone ? RoutingValue.AUTO : RoutingValue.BUILTIN_DEVICE);
+ if (!status) {
+ Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls");
+ }
+ }
+
+ private void clearMicrophoneForCalls() {
+ boolean status = mRoutingHelper.clearPreferredInputDeviceForCalls();
+ if (!status) {
+ Log.d(TAG, "Fail to configure clearMicrophoneForCalls");
+ }
+ }
+
private void setAudioRoutingConfig(CachedBluetoothDevice device) {
AudioDeviceAttributes hearingDeviceAttributes =
- mRoutingHelper.getMatchedHearingDeviceAttributes(device);
+ mRoutingHelper.getMatchedHearingDeviceAttributesForOutput(device);
if (hearingDeviceAttributes == null) {
Log.w(TAG, "Can not find expected AudioDeviceAttributes for hearing device: "
+ device.getDevice().getAnonymizedAddress());
@@ -321,17 +344,13 @@ public class HearingAidDeviceManager {
}
final int callRoutingValue = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.HEARING_AID_CALL_ROUTING,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ Settings.Secure.HEARING_AID_CALL_ROUTING, RoutingValue.AUTO);
final int mediaRoutingValue = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.HEARING_AID_MEDIA_ROUTING,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ Settings.Secure.HEARING_AID_MEDIA_ROUTING, RoutingValue.AUTO);
final int ringtoneRoutingValue = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ Settings.Secure.HEARING_AID_RINGTONE_ROUTING, RoutingValue.AUTO);
final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
- Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING, RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
@@ -351,21 +370,21 @@ public class HearingAidDeviceManager {
// Don't need to pass hearingDevice when we want to reset it (set to AUTO).
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
- /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ /* hearingDevice = */ null, RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
- /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ /* hearingDevice = */ null, RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTES,
- /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ /* hearingDevice = */ null, RoutingValue.AUTO);
setPreferredDeviceRoutingStrategies(
HearingAidAudioRoutingConstants.NOTIFICATION_ROUTING_ATTRIBUTES,
- /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ /* hearingDevice = */ null, RoutingValue.AUTO);
}
private void setPreferredDeviceRoutingStrategies(int[] attributeSdkUsageList,
AudioDeviceAttributes hearingDevice,
- @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+ @RoutingValue int routingValue) {
final List<AudioProductStrategy> supportedStrategies =
mRoutingHelper.getSupportedStrategies(attributeSdkUsageList);
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 714f9519f378..f6acac17e6fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -18,8 +18,6 @@ package com.android.settingslib.mobile.dataservice;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotMapping;
public class DataServiceUtils {
@@ -43,35 +41,6 @@ public class DataServiceUtils {
* {@see MobileNetworkUtils#isMobileDataEnabled(Context)}.
*/
public static final String COLUMN_IS_MOBILE_DATA_ENABLED = "isMobileDataEnabled";
-
- /**
- * The name of the show toggle for physicalSim state column,
- * {@see SubscriptionUtil#showToggleForPhysicalSim(SubscriptionManager)}.
- */
- public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim";
- }
-
- /**
- * Represents columns of the UiccInfoData table, define these columns from
- * {@link android.telephony.UiccSlotInfo}, {@link android.telephony.UiccCardInfo},
- * {@link UiccSlotMapping} and {@link android.telephony.UiccPortInfo}.If columns of these 4
- * classes are changed, we should also update the table except PII data.
- */
- public static final class UiccInfoData {
-
- /** The name of the UiccInfoData table. */
- public static final String TABLE_NAME = "uiccInfo";
-
- /**
- * The name of the ID column, set the {@link SubscriptionInfo#getSubscriptionId()}
- * as the primary key.
- */
- public static final String COLUMN_ID = "sudId";
-
- /**
- * The name of the active state column, see {@link UiccPortInfo#isActive()}.
- */
- public static final String COLUMN_IS_ACTIVE = "isActive";
}
/**
@@ -139,12 +108,5 @@ public class DataServiceUtils {
* {@link SubscriptionManager#isActiveSubscriptionId(int)}.
*/
public static final String COLUMN_IS_ACTIVE_SUBSCRIPTION_ID = "isActiveSubscription";
-
- /**
- * The name of the active data subscription state column, see
- * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
- */
- public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
- "isActiveDataSubscriptionId";
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
index 5f7fa278082b..63573825df02 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -27,7 +27,7 @@ import androidx.room.RoomDatabase;
import java.util.List;
import java.util.Objects;
-@Database(entities = {SubscriptionInfoEntity.class, UiccInfoEntity.class,
+@Database(entities = {SubscriptionInfoEntity.class,
MobileNetworkInfoEntity.class}, exportSchema = false, version = 1)
public abstract class MobileNetworkDatabase extends RoomDatabase {
@@ -35,8 +35,6 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
public abstract SubscriptionInfoDao mSubscriptionInfoDao();
- public abstract UiccInfoDao mUiccInfoDao();
-
public abstract MobileNetworkInfoDao mMobileNetworkInfoDao();
private static MobileNetworkDatabase sInstance;
@@ -73,16 +71,6 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
}
/**
- * Insert the UICC info to the UiccInfoEntity table.
- *
- * @param uiccInfoEntity The uiccInfoEntity.
- */
- public void insertUiccInfo(UiccInfoEntity... uiccInfoEntity) {
- Log.d(TAG, "insertUiccInfo");
- mUiccInfoDao().insertUiccInfo(uiccInfoEntity);
- }
-
- /**
* Insert the mobileNetwork info to the MobileNetworkInfoEntity table.
*
* @param mobileNetworkInfoEntity The mobileNetworkInfoEntity.
@@ -100,14 +88,6 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
}
/**
- * Query the subscription info by the subscription ID from the SubscriptionInfoEntity
- * table.
- */
- public SubscriptionInfoEntity querySubInfoById(String id) {
- return mSubscriptionInfoDao().querySubInfoById(id);
- }
-
- /**
* Query all mobileNetwork infos from the MobileNetworkInfoEntity
* table.
*/
@@ -116,21 +96,6 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
}
/**
- * Query the mobileNetwork info by the subscription ID from the MobileNetworkInfoEntity
- * table.
- */
- public MobileNetworkInfoEntity queryMobileNetworkInfoById(String id) {
- return mMobileNetworkInfoDao().queryMobileNetworkInfoBySubId(id);
- }
-
- /**
- * Query all UICC infos from the UiccInfoEntity table.
- */
- public LiveData<List<UiccInfoEntity>> queryAllUiccInfo() {
- return mUiccInfoDao().queryAllUiccInfos();
- }
-
- /**
* Delete the subscriptionInfo info by the subscription ID from the SubscriptionInfoEntity
* table.
*/
@@ -145,11 +110,4 @@ public abstract class MobileNetworkDatabase extends RoomDatabase {
public void deleteMobileNetworkInfoBySubId(String id) {
mMobileNetworkInfoDao().deleteBySubId(id);
}
-
- /**
- * Delete the UICC info by the subscription ID from the UiccInfoEntity table.
- */
- public void deleteUiccInfoBySubId(String id) {
- mUiccInfoDao().deleteBySubId(id);
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
index 13f99e9387de..6366708cfb17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
@@ -26,11 +26,9 @@ import androidx.room.PrimaryKey;
@Entity(tableName = DataServiceUtils.MobileNetworkInfoData.TABLE_NAME)
public class MobileNetworkInfoEntity {
- public MobileNetworkInfoEntity(@NonNull String subId, boolean isMobileDataEnabled,
- boolean showToggleForPhysicalSim) {
+ public MobileNetworkInfoEntity(@NonNull String subId, boolean isMobileDataEnabled) {
this.subId = subId;
this.isMobileDataEnabled = isMobileDataEnabled;
- this.showToggleForPhysicalSim = showToggleForPhysicalSim;
}
@PrimaryKey
@@ -41,15 +39,11 @@ public class MobileNetworkInfoEntity {
@ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED)
public boolean isMobileDataEnabled;
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM)
- public boolean showToggleForPhysicalSim;
-
@Override
public int hashCode() {
int result = 17;
result = 31 * result + subId.hashCode();
result = 31 * result + Boolean.hashCode(isMobileDataEnabled);
- result = 31 * result + Boolean.hashCode(showToggleForPhysicalSim);
return result;
}
@@ -64,8 +58,7 @@ public class MobileNetworkInfoEntity {
MobileNetworkInfoEntity info = (MobileNetworkInfoEntity) obj;
return TextUtils.equals(subId, info.subId)
- && isMobileDataEnabled == info.isMobileDataEnabled
- && showToggleForPhysicalSim == info.showToggleForPhysicalSim;
+ && isMobileDataEnabled == info.isMobileDataEnabled;
}
public String toString() {
@@ -74,8 +67,6 @@ public class MobileNetworkInfoEntity {
.append(subId)
.append(", isMobileDataEnabled = ")
.append(isMobileDataEnabled)
- .append(", activeNetworkIsCellular = ")
- .append(showToggleForPhysicalSim)
.append(")}");
return builder.toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 88e6a57bf45b..ff0f27b05cd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -30,7 +30,7 @@ public class SubscriptionInfoEntity {
public SubscriptionInfoEntity(@NonNull String subId, int simSlotIndex, boolean isEmbedded,
boolean isOpportunistic, String uniqueName, boolean isSubscriptionVisible,
boolean isDefaultSubscriptionSelection, boolean isValidSubscription,
- boolean isActiveSubscriptionId, boolean isActiveDataSubscriptionId) {
+ boolean isActiveSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.isEmbedded = isEmbedded;
@@ -40,7 +40,6 @@ public class SubscriptionInfoEntity {
this.isDefaultSubscriptionSelection = isDefaultSubscriptionSelection;
this.isValidSubscription = isValidSubscription;
this.isActiveSubscriptionId = isActiveSubscriptionId;
- this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@PrimaryKey
@@ -73,17 +72,10 @@ public class SubscriptionInfoEntity {
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID)
public boolean isActiveSubscriptionId;
- @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
- public boolean isActiveDataSubscriptionId;
-
public int getSubId() {
return Integer.valueOf(subId);
}
- public CharSequence getUniqueDisplayName() {
- return uniqueName;
- }
-
public boolean isActiveSubscription() {
return isActiveSubscriptionId;
}
@@ -103,8 +95,7 @@ public class SubscriptionInfoEntity {
isSubscriptionVisible,
isDefaultSubscriptionSelection,
isValidSubscription,
- isActiveSubscriptionId,
- isActiveDataSubscriptionId);
+ isActiveSubscriptionId);
}
@Override
@@ -125,8 +116,7 @@ public class SubscriptionInfoEntity {
&& isSubscriptionVisible == info.isSubscriptionVisible
&& isDefaultSubscriptionSelection == info.isDefaultSubscriptionSelection
&& isValidSubscription == info.isValidSubscription
- && isActiveSubscriptionId == info.isActiveSubscriptionId
- && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
+ && isActiveSubscriptionId == info.isActiveSubscriptionId;
}
public String toString() {
@@ -149,8 +139,6 @@ public class SubscriptionInfoEntity {
.append(isValidSubscription)
.append(", isActiveSubscriptionId = ")
.append(isActiveSubscriptionId)
- .append(", isActiveDataSubscriptionId = ")
- .append(isActiveDataSubscriptionId)
.append(")}");
return builder.toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
deleted file mode 100644
index 90e5189fdf1d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoDao.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.mobile.dataservice;
-
-import androidx.lifecycle.LiveData;
-import androidx.room.Dao;
-import androidx.room.Insert;
-import androidx.room.OnConflictStrategy;
-import androidx.room.Query;
-
-import java.util.List;
-
-@Dao
-public interface UiccInfoDao {
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- void insertUiccInfo(UiccInfoEntity... uiccInfo);
-
- @Query("SELECT * FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " ORDER BY "
- + DataServiceUtils.UiccInfoData.COLUMN_ID)
- LiveData<List<UiccInfoEntity>> queryAllUiccInfos();
-
- @Query("SELECT COUNT(*) FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME)
- int count();
-
- @Query("DELETE FROM " + DataServiceUtils.UiccInfoData.TABLE_NAME + " WHERE "
- + DataServiceUtils.UiccInfoData.COLUMN_ID + " = :id")
- void deleteBySubId(String id);
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
deleted file mode 100644
index 0f80edf52d80..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/UiccInfoEntity.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.mobile.dataservice;
-
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.room.ColumnInfo;
-import androidx.room.Entity;
-import androidx.room.PrimaryKey;
-
-@Entity(tableName = DataServiceUtils.UiccInfoData.TABLE_NAME)
-public class UiccInfoEntity {
-
- public UiccInfoEntity(@NonNull String subId, boolean isActive) {
- this.subId = subId;
- this.isActive = isActive;
- }
-
- @PrimaryKey
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_ID, index = true)
- @NonNull
- public String subId;
-
- @ColumnInfo(name = DataServiceUtils.UiccInfoData.COLUMN_IS_ACTIVE)
- public boolean isActive;
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + subId.hashCode();
- result = 31 * result + Boolean.hashCode(isActive);
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof UiccInfoEntity)) {
- return false;
- }
-
- UiccInfoEntity info = (UiccInfoEntity) obj;
- return TextUtils.equals(subId, info.subId) && isActive == info.isActive;
- }
-
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(" {UiccInfoEntity(subId = ")
- .append(subId)
- .append(", isActive = ")
- .append(isActive)
- .append(")}");
- return builder.toString();
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
index c83524462b15..dc609bd2fa93 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -16,9 +16,14 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.BUILTIN_MIC;
+import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.BUILTIN_SPEAKER;
+import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.MICROPHONE_SOURCE_VOICE_COMMUNICATION;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -35,10 +40,13 @@ import android.media.audiopolicy.AudioProductStrategy;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -67,28 +75,35 @@ public class HearingAidAudioRoutingHelperTest {
@Spy
private AudioManager mAudioManager = mContext.getSystemService(AudioManager.class);
@Mock
- private AudioDeviceInfo mAudioDeviceInfo;
+ private AudioDeviceInfo mHearingDeviceInfoOutput;
+ @Mock
+ private AudioDeviceInfo mLeHearingDeviceInfoInput;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private CachedBluetoothDevice mSubCachedBluetoothDevice;
- private AudioDeviceAttributes mHearingDeviceAttribute;
+ private AudioDeviceAttributes mHearingDeviceAttributeOutput;
private HearingAidAudioRoutingHelper mHelper;
@Before
public void setUp() {
doReturn(mAudioManager).when(mContext).getSystemService(AudioManager.class);
- when(mAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
- when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ when(mHearingDeviceInfoOutput.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+ when(mHearingDeviceInfoOutput.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ when(mLeHearingDeviceInfoInput.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+ when(mLeHearingDeviceInfoInput.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
- new AudioDeviceInfo[]{mAudioDeviceInfo});
+ new AudioDeviceInfo[]{mHearingDeviceInfoOutput});
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{mLeHearingDeviceInfoInput});
doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
any(AudioProductStrategy.class));
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
- mHearingDeviceAttribute = new AudioDeviceAttributes(
+ mHearingDeviceAttributeOutput = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HEARING_AID,
TEST_DEVICE_ADDRESS);
@@ -99,11 +114,10 @@ public class HearingAidAudioRoutingHelperTest {
@Test
public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
- mHearingDeviceAttribute);
+ mHearingDeviceAttributeOutput);
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
- mHearingDeviceAttribute,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ mHearingDeviceAttributeOutput, RoutingValue.AUTO);
verify(mAudioManager, atLeastOnce()).removePreferredDeviceForStrategy(mAudioStrategy);
}
@@ -113,8 +127,7 @@ public class HearingAidAudioRoutingHelperTest {
when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
- mHearingDeviceAttribute,
- HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ mHearingDeviceAttributeOutput, RoutingValue.AUTO);
verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
}
@@ -122,63 +135,95 @@ public class HearingAidAudioRoutingHelperTest {
@Test
public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
- mHearingDeviceAttribute,
- HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE);
+ mHearingDeviceAttributeOutput, RoutingValue.HEARING_DEVICE);
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
- mHearingDeviceAttribute);
+ mHearingDeviceAttributeOutput);
}
@Test
- public void setPreferredDeviceRoutingStrategies_valueDeviceSpeaker_callSetStrategy() {
- final AudioDeviceAttributes speakerDevice = new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+ public void setPreferredDeviceRoutingStrategies_valueBuiltinDevice_callSetStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
- mHearingDeviceAttribute,
- HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER);
+ mHearingDeviceAttributeOutput, RoutingValue.BUILTIN_DEVICE);
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
- speakerDevice);
+ BUILTIN_SPEAKER);
}
@Test
- public void getMatchedHearingDeviceAttributes_mainHearingDevice_equalAddress() {
+ public void getMatchedHearingDeviceAttributesForOutput_mainHearingDevice_equalAddress() {
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
- final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributesForOutput(
mCachedBluetoothDevice).getAddress();
- assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttributeOutput.getAddress());
}
@Test
- public void getMatchedHearingDeviceAttributes_subHearingDevice_equalAddress() {
+ public void getMatchedHearingDeviceAttributesForOutput_subHearingDevice_equalAddress() {
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
- final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributesForOutput(
mCachedBluetoothDevice).getAddress();
- assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttributeOutput.getAddress());
}
@Test
- public void getMatchedHearingDeviceAttributes_memberHearingDevice_equalAddress() {
+ public void getMatchedHearingDeviceAttributesForOutput_memberHearingDevice_equalAddress() {
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
- final Set<CachedBluetoothDevice> memberDevices = new HashSet<CachedBluetoothDevice>();
+ final Set<CachedBluetoothDevice> memberDevices = new HashSet<>();
memberDevices.add(mSubCachedBluetoothDevice);
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberDevices);
- final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributesForOutput(
mCachedBluetoothDevice).getAddress();
- assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttributeOutput.getAddress());
+ }
+
+ @Test
+ public void setPreferredInputDeviceForCalls_valueAuto_callClearPreset() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+
+ mHelper.setPreferredInputDeviceForCalls(mCachedBluetoothDevice, RoutingValue.AUTO);
+
+ verify(mAudioManager).clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ }
+
+ @Test
+ public void setPreferredInputDeviceForCalls_valueHearingDevice_callSetPresetToHearingDevice() {
+ final ArgumentCaptor<AudioDeviceAttributes> audioDeviceAttributesCaptor =
+ ArgumentCaptor.forClass(AudioDeviceAttributes.class);
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+
+ mHelper.setPreferredInputDeviceForCalls(mCachedBluetoothDevice,
+ RoutingValue.HEARING_DEVICE);
+
+ verify(mAudioManager).setPreferredDeviceForCapturePreset(
+ eq(MICROPHONE_SOURCE_VOICE_COMMUNICATION), audioDeviceAttributesCaptor.capture());
+ assertThat(audioDeviceAttributesCaptor.getValue().getAddress()).isEqualTo(
+ TEST_DEVICE_ADDRESS);
+ }
+
+ @Test
+ public void setPreferredInputDeviceForCalls_valueBuiltinDevice_callClearPresetToBuiltinMic() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+
+ mHelper.setPreferredInputDeviceForCalls(mCachedBluetoothDevice,
+ RoutingValue.BUILTIN_DEVICE);
+
+ verify(mAudioManager).setPreferredDeviceForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION, BUILTIN_MIC);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index eb73eee90f0d..2458c5b2eb6e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -729,7 +729,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_connected_callSetStrategies() {
- when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+ when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(),
@@ -743,7 +743,7 @@ public class HearingAidDeviceManagerTest {
@Test
public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
- when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+ when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
mHearingDeviceAttribute);
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 18bebd40b03a..b9f8c714175e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -289,5 +289,6 @@ public class SecureSettings {
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
Settings.Secure.ADVANCED_PROTECTION_MODE,
Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
+ Settings.Secure.EM_VALUE,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1d7608d7d4d0..7c5e577b1d93 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -445,6 +445,8 @@ public class SecureSettingsValidators {
Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
new InclusiveIntegerRangeValidator(0, 10));
+ VALIDATORS.put(Secure.EM_VALUE,
+ new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.CHARGE_OPTIMIZATION_MODE, new InclusiveIntegerRangeValidator(0, 10));
VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 661a09553914..dedd7ebd1ef7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2186,6 +2186,10 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.EvenDimmer.EVEN_DIMMER_MIN_NITS);
p.end(evenDimmerToken);
+ dumpSetting(s, p,
+ Settings.Secure.EM_VALUE,
+ SecureSettingsProto.Accessibility.EM_VALUE);
+
final long gestureToken = p.start(SecureSettingsProto.GESTURE);
dumpSetting(s, p,
Settings.Secure.AWARE_ENABLED,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9ab281217fbd..9c53afecad11 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -20,6 +20,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
@@ -43,6 +44,7 @@ import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
+import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -158,6 +160,7 @@ fun CommunalContainer(
transitions = sceneTransitions,
)
}
+ val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
val detector = remember { CommunalSwipeDetector() }
@@ -174,9 +177,11 @@ fun CommunalContainer(
onDispose { viewModel.setTransitionState(null) }
}
+ val blurRadius = with(LocalDensity.current) { viewModel.blurRadiusPx.toDp() }
+
SceneTransitionLayout(
state = state,
- modifier = modifier.fillMaxSize(),
+ modifier = modifier.fillMaxSize().thenIf(isUiBlurred) { Modifier.blur(blurRadius) },
swipeSourceDetector = detector,
swipeDetector = detector,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 3ff99a8d1758..46e0efa79ef5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -361,8 +361,9 @@ fun ContentScope.NotificationScrollingStack(
val shadeScrollState by remember {
derivedStateOf {
ShadeScrollState(
- // we are not scrolled to the top unless the scrim is at its maximum offset.
- isScrolledToTop = scrimOffset.value >= 0f,
+ // we are not scrolled to the top unless the scroll position is zero,
+ // and the scrim is at its maximum offset
+ isScrolledToTop = scrimOffset.value >= 0f && scrollState.value == 0,
scrollPosition = scrollState.value,
maxScrollPosition = scrollState.maxValue,
)
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 3327fc020da3..f052e60246d2 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
@@ -103,12 +103,15 @@ constructor(
onScrimClicked = viewModel::onScrimClicked,
) {
Column {
- CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
- )
+ if (viewModel.showHeader) {
+ CollapsedShadeHeader(
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+ createTintedIconManager = tintedIconManagerFactory::create,
+ createBatteryMeterViewController =
+ batteryMeterViewControllerFactory::create,
+ statusBarIconController = statusBarIconController,
+ )
+ }
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
@@ -178,8 +181,8 @@ fun ContentScope.QuickSettingsLayout(
Column(
verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
horizontalAlignment = Alignment.CenterHorizontally,
- modifier = modifier
- .padding(
+ modifier =
+ modifier.padding(
start = QuickSettingsShade.Dimensions.Padding,
end = QuickSettingsShade.Dimensions.Padding,
bottom = QuickSettingsShade.Dimensions.Padding,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index d70af2806430..b70f46c4b01c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -25,10 +25,12 @@ import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
@@ -69,6 +71,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
@@ -184,6 +187,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
logcatLogBuffer("CommunalViewModelTest"),
metricsLogger,
kosmos.mediaCarouselController,
+ kosmos.blurConfig,
)
}
@@ -893,6 +897,20 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(selectedKey2).isEqualTo(key)
}
+ @Test
+ @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+ fun uiIsBlurred_whenPrimaryBouncerIsShowing() =
+ testScope.runTest {
+ val viewModel = createViewModel()
+ val isUiBlurred by collectLastValue(viewModel.isUiBlurred)
+
+ kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ assertThat(isUiBlurred).isTrue()
+
+ kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false)
+ assertThat(isUiBlurred).isFalse()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
index f8f6fe246563..466c9f96749f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -26,6 +26,7 @@ import androidx.media3.common.Player
import androidx.media3.session.CommandButton
import androidx.media3.session.MediaController as Media3Controller
import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionResult
import androidx.media3.session.SessionToken
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -41,6 +42,8 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.execution
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -60,6 +63,7 @@ private const val PACKAGE_NAME = "package_name"
private const val CUSTOM_ACTION_NAME = "Custom Action"
private const val CUSTOM_ACTION_COMMAND = "custom-action"
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper
@RunWith(AndroidJUnit4::class)
@@ -84,12 +88,14 @@ class Media3ActionFactoryTest : SysuiTestCase() {
}
}
private val customLayout = ImmutableList.of<CommandButton>()
+ private val customCommandFuture = mock<ListenableFuture<SessionResult>>()
private val media3Controller =
mock<Media3Controller> {
on { customLayout } doReturn customLayout
on { sessionExtras } doReturn Bundle()
on { isCommandAvailable(any()) } doReturn true
on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+ on { sendCustomCommand(any(), any()) } doReturn customCommandFuture
}
private lateinit var underTest: Media3ActionFactory
@@ -105,7 +111,7 @@ class Media3ActionFactoryTest : SysuiTestCase() {
kosmos.mediaLogger,
kosmos.looper,
handler,
- kosmos.testScope,
+ testScope,
kosmos.execution,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 24f6b6d8566b..7ab8ab93c024 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
@@ -124,6 +125,24 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
}
+ @Test
+ fun showHeader_showsOnNarrowScreen() =
+ testScope.runTest {
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+ runCurrent()
+
+ assertThat(underTest.showHeader).isTrue()
+ }
+
+ @Test
+ fun showHeader_hidesOnWideScreen() =
+ testScope.runTest {
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ runCurrent()
+
+ assertThat(underTest.showHeader).isFalse()
+ }
+
private fun TestScope.lockDevice() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
kosmos.powerInteractor.setAsleepForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
new file mode 100644
index 000000000000..8650e4b8cfce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.statusbar.featurepods.media.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaControlChipViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val underTest = kosmos.mediaControlChipViewModel
+
+ @Test
+ fun chip_noActiveMedia_IsHidden() =
+ kosmos.runTest {
+ val chip by collectLastValue(underTest.chip)
+
+ assertThat(chip).isInstanceOf(PopupChipModel.Hidden::class.java)
+ }
+
+ @Test
+ fun chip_activeMedia_IsShown() =
+ kosmos.runTest {
+ val chip by collectLastValue(underTest.chip)
+
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
+ }
+
+ @Test
+ fun chip_songNameChanges_chipTextUpdated() =
+ kosmos.runTest {
+ val chip by collectLastValue(underTest.chip)
+
+ val initialSongName = "Initial Song"
+ val newSongName = "New Song"
+ val userMedia = MediaData(active = true, song = initialSongName)
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
+
+ val updatedUserMedia = userMedia.copy(song = newSongName)
+ mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+
+ assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
index 74d7e19ea86c..fcbf0fe9a37a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
@@ -22,6 +22,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -35,12 +39,28 @@ import org.junit.runner.RunWith
class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val mediaFilterRepository = kosmos.mediaFilterRepository
private val underTest = kosmos.statusBarPopupChipsViewModel
@Test
- fun popupChips_allHidden_empty() =
+ fun shownPopupChips_allHidden_empty() =
testScope.runTest {
- val latest by collectLastValue(underTest.popupChips)
- assertThat(latest).isEmpty()
+ val shownPopupChips by collectLastValue(underTest.shownPopupChips)
+ assertThat(shownPopupChips).isEmpty()
+ }
+
+ @Test
+ fun shownPopupChips_activeMedia_restHidden_mediaControlChipShown() =
+ testScope.runTest {
+ val shownPopupChips by collectLastValue(underTest.shownPopupChips)
+
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(shownPopupChips).hasSize(1)
+ assertThat(shownPopupChips!!.first().chipId).isEqualTo(PopupChipId.MediaControl)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 1a1af2eecc00..87abd0a4494a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.notification.stack
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -20,6 +21,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -30,6 +32,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -59,7 +62,7 @@ open class NotificationShelfTest : SysuiTestCase() {
.inflate(
/* resource = */ R.layout.status_bar_notification_shelf,
/* root = */ root,
- /* attachToRoot = */ false
+ /* attachToRoot = */ false,
) as NotificationShelf
whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
@@ -128,6 +131,177 @@ open class NotificationShelfTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testAlignment_splitShade_LTR() {
+ // Given: LTR mode, split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = true, width = 100, actualWidth = 40)
+
+ // Then: shelf should align to end
+ assertTrue(shelfSpy.isAlignedToEnd)
+ assertTrue(shelfSpy.isAlignedToRight)
+ assertTrue(shelfSpy.mBackgroundNormal.alignToEnd)
+ assertTrue(shelfSpy.mShelfIcons.alignToEnd)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testAlignment_nonSplitShade_LTR() {
+ // Given: LTR mode, non split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = false, width = 100, actualWidth = 40)
+
+ // Then: shelf should not align to end
+ assertFalse(shelfSpy.isAlignedToEnd)
+ assertFalse(shelfSpy.isAlignedToRight)
+ assertFalse(shelfSpy.mBackgroundNormal.alignToEnd)
+ assertFalse(shelfSpy.mShelfIcons.alignToEnd)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testAlignment_splitShade_RTL() {
+ // Given: RTL mode, split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = true, width = 100, actualWidth = 40)
+
+ // Then: shelf should align to end, but to left due to RTL
+ assertTrue(shelfSpy.isAlignedToEnd)
+ assertFalse(shelfSpy.isAlignedToRight)
+ assertTrue(shelfSpy.mBackgroundNormal.alignToEnd)
+ assertTrue(shelfSpy.mShelfIcons.alignToEnd)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testAlignment_nonSplitShade_RTL() {
+ // Given: RTL mode, non split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = false, width = 100, actualWidth = 40)
+
+ // Then: shelf should not align to end, but to right due to RTL
+ assertFalse(shelfSpy.isAlignedToEnd)
+ assertTrue(shelfSpy.isAlignedToRight)
+ assertFalse(shelfSpy.mBackgroundNormal.alignToEnd)
+ assertFalse(shelfSpy.mShelfIcons.alignToEnd)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfLeftBound_splitShade_LTR() {
+ // Given: LTR mode, split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = true, width = 100, actualWidth = 40)
+
+ // When: get the left bound of the shelf
+ val shelfLeftBound = shelfSpy.shelfLeftBound
+
+ // Then: should be equal to shelf's width - actual width
+ assertEquals(60f, shelfLeftBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfRightBound_splitShade_LTR() {
+ // Given: LTR mode, split shade, width 100, actual width 40
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = true, width = 100, actualWidth = 40)
+
+ // Then: the right bound of the shelf should be equal to shelf's width
+ assertEquals(100f, shelfSpy.shelfRightBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfLeftBound_nonSplitShade_LTR() {
+ // Given: LTR mode, non split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = false, width = 100, actualWidth = 40)
+
+ // When: get the left bound of the shelf
+ val shelfLeftBound = shelfSpy.shelfLeftBound
+
+ // Then: should be equal to 0f
+ assertEquals(0f, shelfLeftBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfRightBound_nonSplitShade_LTR() {
+ // Given: LTR mode, non split shade, width 100, actual width 40
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = false, splitShade = false, width = 100, actualWidth = 40)
+
+ // Then: the right bound of the shelf should be equal to shelf's actual width
+ assertEquals(40f, shelfSpy.shelfRightBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfLeftBound_splitShade_RTL() {
+ // Given: RTL mode, split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = true, width = 100, actualWidth = 40)
+
+ // When: get the left bound of the shelf
+ val shelfLeftBound = shelfSpy.shelfLeftBound
+
+ // Then: should be equal to 0f
+ assertEquals(0f, shelfLeftBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfRightBound_splitShade_RTL() {
+ // Given: RTL mode, split shade, width 100, actual width 40
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = true, width = 100, actualWidth = 40)
+
+ // Then: the right bound of the shelf should be equal to shelf's actual width
+ assertEquals(40f, shelfSpy.shelfRightBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfLeftBound_nonSplitShade_RTL() {
+ // Given: RTL mode, non split shade
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = false, width = 100, actualWidth = 40)
+
+ // When: get the left bound of the shelf
+ val shelfLeftBound = shelfSpy.shelfLeftBound
+
+ // Then: should be equal to shelf's width - actual width
+ assertEquals(60f, shelfLeftBound)
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
+ fun testGetShelfRightBound_nonSplitShade_RTL() {
+ // Given: LTR mode, non split shade, width 100, actual width 40
+ val shelfSpy =
+ prepareShelfSpy(shelf, rtl = true, splitShade = false, width = 100, actualWidth = 40)
+
+ // Then: the right bound of the shelf should be equal to shelf's width
+ assertEquals(100f, shelfSpy.shelfRightBound)
+ }
+
+ private fun prepareShelfSpy(
+ shelf: NotificationShelf,
+ rtl: Boolean,
+ splitShade: Boolean,
+ width: Int,
+ actualWidth: Int,
+ ): NotificationShelf {
+ val shelfSpy = spy(shelf)
+ whenever(shelfSpy.isLayoutRtl).thenReturn(rtl)
+ whenever(ambientState.useSplitShade).thenReturn(splitShade)
+ whenever(shelfSpy.width).thenReturn(width)
+ shelfSpy.setActualWidth(actualWidth.toFloat())
+ return shelfSpy
+ }
+
+ @Test
fun getAmountInShelf_lastViewBelowShelf_completelyInShelf() {
val shelfClipStart = 0f
val viewStart = 1f
@@ -152,7 +326,7 @@ open class NotificationShelfTest : SysuiTestCase() {
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
- shelfClipStart
+ shelfClipStart,
)
assertEquals(1f, amountInShelf)
}
@@ -182,7 +356,7 @@ open class NotificationShelfTest : SysuiTestCase() {
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
- shelfClipStart
+ shelfClipStart,
)
assertEquals(1f, amountInShelf)
}
@@ -212,7 +386,7 @@ open class NotificationShelfTest : SysuiTestCase() {
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
- shelfClipStart
+ shelfClipStart,
)
assertEquals(0.5f, amountInShelf)
}
@@ -241,7 +415,7 @@ open class NotificationShelfTest : SysuiTestCase() {
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
- shelfClipStart
+ shelfClipStart,
)
assertEquals(0f, amountInShelf)
}
@@ -250,7 +424,7 @@ open class NotificationShelfTest : SysuiTestCase() {
fun updateState_expansionChanging_shelfTransparent() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.25f,
- expectedAlpha = 0.0f
+ expectedAlpha = 0.0f,
)
}
@@ -260,7 +434,7 @@ open class NotificationShelfTest : SysuiTestCase() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.85f,
- expectedAlpha = 0.0f
+ expectedAlpha = 0.0f,
)
}
@@ -281,7 +455,7 @@ open class NotificationShelfTest : SysuiTestCase() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = expansionFraction,
- expectedAlpha = 0.123f
+ expectedAlpha = 0.123f,
)
}
@@ -330,7 +504,7 @@ open class NotificationShelfTest : SysuiTestCase() {
/* scrollingFast= */ false,
/* expandingAnimated= */ false,
/* isLastChild= */ true,
- shelfClipStart
+ shelfClipStart,
)
assertEquals(1f, amountInShelf)
}
@@ -628,7 +802,7 @@ open class NotificationShelfTest : SysuiTestCase() {
private fun updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction: Float,
- expectedAlpha: Float
+ expectedAlpha: Float,
) {
val sbnMock: StatusBarNotification = mock()
val mockEntry = mock<NotificationEntry>().apply { whenever(this.sbn).thenReturn(sbnMock) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 0aaf89a4c382..a9db0b70dd4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -41,6 +42,8 @@ class FakeHomeStatusBarViewModel(
override val ongoingActivityChips = MutableStateFlow(MultipleOngoingActivityChipsModel())
+ override val statusBarPopupChips = MutableStateFlow(emptyList<PopupChipModel.Shown>())
+
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
override val shouldShowOperatorNameView = MutableStateFlow(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt
new file mode 100644
index 000000000000..68b5772bd7ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.touchpad.tutorial.ui.gesture
+
+import android.graphics.PointF
+import android.view.MotionEvent
+import kotlin.math.cos
+import kotlin.math.sin
+
+/** Test helper to generate circular gestures or full EasterEgg gesture */
+object EasterEggGesture {
+
+ fun motionEventsForGesture(): List<MotionEvent> {
+ val gesturePath = generateCircularGesturePoints(circlesCount = 3)
+ val events =
+ TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { p -> move(p.x, p.y) } }
+ return events
+ }
+
+ /**
+ * Generates list of points that would make up clockwise circular motion with given [radius].
+ * [circlesCount] determines how many full circles gesture should perform. [radiusNoiseFraction]
+ * can introduce noise to mimic real-world gesture which is not perfect - shape will be still
+ * circular but radius at any given point can be deviate from given radius by
+ * [radiusNoiseFraction].
+ */
+ fun generateCircularGesturePoints(
+ circlesCount: Int,
+ radiusNoiseFraction: Double? = null,
+ radius: Float = 100f,
+ ): List<PointF> {
+ val pointsPerCircle = 50
+ val angleStep = 360 / pointsPerCircle
+ val angleBuffer = 20 // buffer to make sure we're doing a bit more than 360 degree
+ val totalAngle = circlesCount * (360 + angleBuffer)
+ // Because all gestures in tests should start at (DEFAULT_X, DEFAULT_Y) we need to shift
+ // circle center x coordinate by radius
+ val centerX = -radius
+ val centerY = 0f
+
+ val randomNoise: (Double) -> Double =
+ if (radiusNoiseFraction == null) {
+ { 0.0 }
+ } else {
+ { radianAngle -> sin(radianAngle * 2) * radiusNoiseFraction }
+ }
+
+ val events = mutableListOf<PointF>()
+ var currentAngle = 0f
+ // as cos(0) == 1 and sin(0) == 0 we start gesture at position of (radius, 0) and go
+ // clockwise - first Y increases and X decreases
+ while (currentAngle < totalAngle) {
+ val radianAngle = Math.toRadians(currentAngle.toDouble())
+ val radiusWithNoise = radius * (1 + randomNoise(radianAngle).toFloat())
+ val x = centerX + radiusWithNoise * cos(radianAngle).toFloat()
+ val y = centerY + radiusWithNoise * sin(radianAngle).toFloat()
+ events.add(PointF(x, y))
+ currentAngle += angleStep
+ }
+ return events
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizerTest.kt
index ff0cec5e06e9..3829778864f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizerTest.kt
@@ -16,29 +16,28 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.graphics.PointF
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGesture.generateCircularGesturePoints
import com.google.common.truth.Truth.assertThat
-import kotlin.math.cos
-import kotlin.math.sin
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class EasterEggGestureTest : SysuiTestCase() {
-
- private data class Point(val x: Float, val y: Float)
+class EasterEggGestureRecognizerTest : SysuiTestCase() {
private var triggered = false
- private val handler =
- TouchpadGestureHandler(
- BackGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()),
- EasterEggGestureMonitor(callback = { triggered = true }),
- )
+ private val gestureRecognizer = EasterEggGestureRecognizer()
+
+ @Before
+ fun setup() {
+ gestureRecognizer.addGestureStateCallback { triggered = it == GestureState.Finished }
+ }
@Test
fun easterEggTriggeredAfterThreeCircles() {
@@ -99,56 +98,13 @@ class EasterEggGestureTest : SysuiTestCase() {
}
private fun assertStateAfterEvents(events: List<MotionEvent>, wasTriggered: Boolean) {
- events.forEach { handler.onMotionEvent(it) }
+ events.forEach { gestureRecognizer.accept(it) }
assertThat(triggered).isEqualTo(wasTriggered)
}
- private fun assertStateAfterTwoFingerGesture(gesturePath: List<Point>, wasTriggered: Boolean) {
+ private fun assertStateAfterTwoFingerGesture(gesturePath: List<PointF>, wasTriggered: Boolean) {
val events =
- TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { (x, y) -> move(x, y) } }
+ TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { p -> move(p.x, p.y) } }
assertStateAfterEvents(events = events, wasTriggered = wasTriggered)
}
-
- /**
- * Generates list of points that would make up clockwise circular motion with given [radius].
- * [circlesCount] determines how many full circles gesture should perform. [radiusNoiseFraction]
- * can introduce noise to mimic real-world gesture which is not perfect - shape will be still
- * circular but radius at any given point can be deviate from given radius by
- * [radiusNoiseFraction].
- */
- private fun generateCircularGesturePoints(
- circlesCount: Int,
- radiusNoiseFraction: Double? = null,
- radius: Float = 100f,
- ): List<Point> {
- val pointsPerCircle = 50
- val angleStep = 360 / pointsPerCircle
- val angleBuffer = 20 // buffer to make sure we're doing a bit more than 360 degree
- val totalAngle = circlesCount * (360 + angleBuffer)
- // Because all gestures in tests should start at (DEFAULT_X, DEFAULT_Y) we need to shift
- // circle center x coordinate by radius
- val centerX = -radius
- val centerY = 0f
-
- val events = mutableListOf<Point>()
- val randomNoise: (Double) -> Double =
- if (radiusNoiseFraction == null) {
- { 0.0 }
- } else {
- { radianAngle -> sin(radianAngle * 2) * radiusNoiseFraction }
- }
-
- var currentAngle = 0f
- // as cos(0) == 1 and sin(0) == 0 we start gesture at position of (radius, 0) and go
- // clockwise - first Y increases and X decreases
- while (currentAngle < totalAngle) {
- val radianAngle = Math.toRadians(currentAngle.toDouble())
- val radiusWithNoise = radius * (1 + randomNoise(radianAngle).toFloat())
- val x = centerX + radiusWithNoise * cos(radianAngle).toFloat()
- val y = centerY + radiusWithNoise * sin(radianAngle).toFloat()
- events.add(Point(x, y))
- currentAngle += angleStep
- }
- return events
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilterTest.kt
index c302b40fc4d7..20bcb3eac8a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilterTest.kt
@@ -33,12 +33,11 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class TouchpadGestureHandlerTest : SysuiTestCase() {
+class TouchpadEventsFilterTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
private val gestureRecognizer =
BackGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
- private val handler = TouchpadGestureHandler(gestureRecognizer, EasterEggGestureMonitor {})
@Before
fun before() {
@@ -48,21 +47,21 @@ class TouchpadGestureHandlerTest : SysuiTestCase() {
@Test
fun handlesEventsFromTouchpad() {
val event = downEvent(source = SOURCE_MOUSE, toolType = TOOL_TYPE_FINGER)
- val eventHandled = handler.onMotionEvent(event)
+ val eventHandled = gestureRecognizer.handleTouchpadMotionEvent(event)
assertThat(eventHandled).isTrue()
}
@Test
fun ignoresEventsFromMouse() {
val event = downEvent(source = SOURCE_MOUSE, toolType = TOOL_TYPE_MOUSE)
- val eventHandled = handler.onMotionEvent(event)
+ val eventHandled = gestureRecognizer.handleTouchpadMotionEvent(event)
assertThat(eventHandled).isFalse()
}
@Test
fun ignoresEventsFromTouch() {
val event = downEvent(source = SOURCE_TOUCHSCREEN, toolType = TOOL_TYPE_FINGER)
- val eventHandled = handler.onMotionEvent(event)
+ val eventHandled = gestureRecognizer.handleTouchpadMotionEvent(event)
assertThat(eventHandled).isFalse()
}
@@ -70,25 +69,10 @@ class TouchpadGestureHandlerTest : SysuiTestCase() {
fun ignoresButtonClicksFromTouchpad() {
val event = downEvent(source = SOURCE_MOUSE, toolType = TOOL_TYPE_FINGER)
event.buttonState = MotionEvent.BUTTON_PRIMARY
- val eventHandled = handler.onMotionEvent(event)
+ val eventHandled = gestureRecognizer.handleTouchpadMotionEvent(event)
assertThat(eventHandled).isFalse()
}
private fun downEvent(source: Int, toolType: Int) =
motionEvent(action = ACTION_DOWN, x = 0f, y = 0f, source = source, toolType = toolType)
-
- @Test
- fun triggersGestureDoneForThreeFingerGesture() {
- backGestureEvents().forEach { handler.onMotionEvent(it) }
-
- assertThat(gestureState).isEqualTo(GestureState.Finished)
- }
-
- private fun backGestureEvents(): List<MotionEvent> {
- return ThreeFingerGesture.eventsForFullGesture {
- move(deltaX = SWIPE_DISTANCE / 4)
- move(deltaX = SWIPE_DISTANCE / 2)
- move(deltaX = SWIPE_DISTANCE)
- }
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
index f90e14caca75..79c1f9fcf517 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
@@ -54,13 +54,6 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithLeftProgressAnimation() =
kosmos.runTest {
assertProgressWhileMovingFingers(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt
new file mode 100644
index 000000000000..4af374287c62
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.touchpad.tutorial.ui.viewmodel
+
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGesture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EasterEggGestureViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val viewModel = EasterEggGestureViewModel()
+
+ @Before
+ fun before() {
+ kosmos.useUnconfinedTestDispatcher()
+ }
+
+ @Test
+ fun easterEggNotTriggeredAtStart() =
+ kosmos.runTest {
+ val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
+ assertThat(easterEggTriggered).isFalse()
+ }
+
+ @Test
+ fun emitsTrueOnEasterEggTriggered() =
+ kosmos.runTest {
+ assertStateAfterEvents(
+ events = EasterEggGesture.motionEventsForGesture(),
+ expected = true,
+ )
+ }
+
+ @Test
+ fun emitsFalseOnEasterEggCallbackExecuted() =
+ kosmos.runTest {
+ val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
+ EasterEggGesture.motionEventsForGesture().forEach { viewModel.accept(it) }
+
+ assertThat(easterEggTriggered).isEqualTo(true)
+ viewModel.onEasterEggFinished()
+ assertThat(easterEggTriggered).isEqualTo(false)
+ }
+
+ private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: Boolean) {
+ val state by collectLastValue(viewModel.easterEggTriggered)
+ events.forEach { viewModel.accept(it) }
+ assertThat(state).isEqualTo(expected)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
index 3c06352ace97..4dfd01a91f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
@@ -70,13 +70,6 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithAnimationMarkers() =
kosmos.runTest {
assertStateAfterEvents(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
index a2d8a8b3cb0e..66bf778a754b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
@@ -74,13 +74,6 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithAnimationMarkers() =
kosmos.runTest {
assertStateAfterEvents(
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
index 58c545036b27..071b07631ff9 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -24,11 +24,11 @@
android:clickable="true"
>
- <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
+ <com.android.systemui.statusbar.notification.shelf.NotificationShelfBackgroundView
android:id="@+id/backgroundNormal"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <com.android.systemui.statusbar.phone.NotificationIconContainer
+ <com.android.systemui.statusbar.notification.shelf.NotificationShelfIconContainer
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0503dbfab71d..94698bcb88b8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -570,6 +570,8 @@
<item name="trackCornerSize">12dp</item>
<item name="trackInsideCornerSize">2dp</item>
<item name="trackStopIndicatorSize">6dp</item>
+ <item name="trackIconSize">20dp</item>
+ <item name="labelBehavior">gone</item>
</style>
<style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
@@ -579,6 +581,7 @@
<item name="tickColorInactive">@androidprv:color/materialColorPrimary</item>
<item name="trackColorActive">@androidprv:color/materialColorPrimary</item>
<item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item>
+ <item name="trackIconActiveColor">@androidprv:color/materialColorSurfaceContainerHighest</item>
</style>
<style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ddc4d1c10690..16cf26393aee 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -33,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.transitions.BlurConfig
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -92,6 +93,7 @@ constructor(
@CommunalLog logBuffer: LogBuffer,
private val metricsLogger: CommunalMetricsLogger,
mediaCarouselController: MediaCarouselController,
+ blurConfig: BlurConfig,
) :
BaseCommunalViewModel(
communalSceneInteractor,
@@ -221,6 +223,15 @@ constructor(
val isEnableWorkProfileDialogShowing: Flow<Boolean> =
_isEnableWorkProfileDialogShowing.asStateFlow()
+ val isUiBlurred: StateFlow<Boolean> =
+ if (Flags.bouncerUiRevamp()) {
+ keyguardInteractor.primaryBouncerShowing
+ } else {
+ MutableStateFlow(false)
+ }
+
+ val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx / 2.0f
+
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
index 913aa6f9d547..09544827a51a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -43,6 +43,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.SessionTokenFactory
import com.android.systemui.res.R
import com.android.systemui.util.concurrency.Execution
+import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -71,7 +72,7 @@ constructor(
*
* @param packageName Package name for the media app
* @param controller The framework [MediaController] for the session
- * @return The media action buttons, or null if the session token is null
+ * @return The media action buttons, or null if cannot be created for this session
*/
suspend fun createActionsFromSession(
packageName: String,
@@ -80,6 +81,10 @@ constructor(
// Get the Media3 controller using the legacy token
val token = tokenFactory.createTokenFromLegacy(sessionToken)
val m3controller = controllerFactory.create(token, looper)
+ if (m3controller == null) {
+ logger.logCreateFailed(packageName, "createActionsFromSession")
+ return null
+ }
// Build button info
val buttons = suspendCancellableCoroutine { continuation ->
@@ -89,13 +94,14 @@ constructor(
val result = getMedia3Actions(packageName, m3controller, token)
continuation.resumeWith(Result.success(result))
} finally {
- m3controller.release()
+ m3controller.tryRelease(packageName, logger)
}
}
handler.post(runnable)
continuation.invokeOnCancellation {
// Ensure controller is released, even if loading was cancelled partway through
- handler.post(m3controller::release)
+ val releaseRunnable = Runnable { m3controller.tryRelease(packageName, logger) }
+ handler.post(releaseRunnable)
handler.removeCallbacks(runnable)
}
}
@@ -127,11 +133,12 @@ constructor(
com.android.internal.R.drawable.progress_small_material,
)
} else {
- getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+ getStandardAction(packageName, m3controller, token, Player.COMMAND_PLAY_PAUSE)
}
val prevButton =
getStandardAction(
+ packageName,
m3controller,
token,
Player.COMMAND_SEEK_TO_PREVIOUS,
@@ -139,6 +146,7 @@ constructor(
)
val nextButton =
getStandardAction(
+ packageName,
m3controller,
token,
Player.COMMAND_SEEK_TO_NEXT,
@@ -208,6 +216,7 @@ constructor(
* @return A [MediaAction] representing the first supported command, or null if not supported
*/
private fun getStandardAction(
+ packageName: String,
controller: Media3Controller,
token: SessionToken,
vararg commands: @Player.Command Int,
@@ -222,14 +231,14 @@ constructor(
if (!controller.isPlaying) {
MediaAction(
context.getDrawable(R.drawable.ic_media_play),
- { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ { executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_play),
context.getDrawable(R.drawable.ic_media_play_container),
)
} else {
MediaAction(
context.getDrawable(R.drawable.ic_media_pause),
- { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ { executeAction(packageName, token, Player.COMMAND_PLAY_PAUSE) },
context.getString(R.string.controls_media_button_pause),
context.getDrawable(R.drawable.ic_media_pause_container),
)
@@ -238,7 +247,7 @@ constructor(
else -> {
MediaAction(
icon = getIconForAction(command),
- action = { executeAction(token, command) },
+ action = { executeAction(packageName, token, command) },
contentDescription = getDescriptionForAction(command),
background = null,
)
@@ -256,7 +265,7 @@ constructor(
): MediaAction {
return MediaAction(
getIconForAction(customAction, packageName),
- { executeAction(token, Player.COMMAND_INVALID, customAction) },
+ { executeAction(packageName, token, Player.COMMAND_INVALID, customAction) },
customAction.displayName,
null,
)
@@ -308,12 +317,17 @@ constructor(
}
private fun executeAction(
+ packageName: String,
token: SessionToken,
command: Int,
customAction: CommandButton? = null,
) {
bgScope.launch {
val controller = controllerFactory.create(token, looper)
+ if (controller == null) {
+ logger.logCreateFailed(packageName, "executeAction")
+ return@launch
+ }
handler.post {
try {
when (command) {
@@ -347,9 +361,17 @@ constructor(
else -> logger.logMedia3UnsupportedCommand(command.toString())
}
} finally {
- controller.release()
+ controller.tryRelease(packageName, logger)
}
}
}
}
}
+
+private fun Media3Controller.tryRelease(packageName: String, logger: MediaLogger) {
+ try {
+ this.release()
+ } catch (e: ExecutionException) {
+ logger.logReleaseFailed(packageName, e.cause.toString())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 0b598c13311f..c52268e35e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -144,6 +144,30 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
}
+ fun logCreateFailed(pkg: String, method: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = pkg
+ str2 = method
+ },
+ { "Controller create failed for $str1 ($str2)" },
+ )
+ }
+
+ fun logReleaseFailed(pkg: String, cause: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = pkg
+ str2 = cause
+ },
+ { "Controller release failed for $str1 ($str2)" },
+ )
+ }
+
companion object {
private const val TAG = "MediaLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
index d815852b790f..7b9e18a0744b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -19,13 +19,17 @@ import android.content.Context
import android.media.session.MediaController
import android.media.session.MediaSession
import android.os.Looper
+import android.util.Log
import androidx.concurrent.futures.await
import androidx.media3.session.MediaController as Media3Controller
import androidx.media3.session.SessionToken
+import java.util.concurrent.ExecutionException
import javax.inject.Inject
/** Testable wrapper for media controller construction */
open class MediaControllerFactory @Inject constructor(private val context: Context) {
+ private val TAG = "MediaControllerFactory"
+
/**
* Creates a new [MediaController] from the framework session token.
*
@@ -41,10 +45,18 @@ open class MediaControllerFactory @Inject constructor(private val context: Conte
* @param token The token for the session
* @param looper The looper that will be used for this controller's operations
*/
- open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
- return Media3Controller.Builder(context, token)
- .setApplicationLooper(looper)
- .buildAsync()
- .await()
+ open suspend fun create(token: SessionToken, looper: Looper): Media3Controller? {
+ try {
+ return Media3Controller.Builder(context, token)
+ .setApplicationLooper(looper)
+ .buildAsync()
+ .await()
+ } catch (e: ExecutionException) {
+ if (e.cause is SecurityException) {
+ // The session rejected the connection
+ Log.d(TAG, "SecurityException creating media3 controller")
+ }
+ return null
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index 8ef51af18881..c0c0aea073f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,8 +16,10 @@
package com.android.systemui.qs.ui.viewmodel
+import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -28,6 +30,8 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/**
* Models UI state used to render the content of the quick settings shade overlay.
@@ -44,10 +48,21 @@ constructor(
quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator")
+
+ val showHeader: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showHeader",
+ initialValue = !shadeInteractor.isShadeLayoutWide.value,
+ source = shadeInteractor.isShadeLayoutWide.map { !it },
+ )
+
val quickSettingsContainerViewModel = quickSettingsContainerViewModelFactory.create(false)
override suspend fun onActivated(): Nothing {
coroutineScope {
+ launch { hydrator.activate() }
+
launch {
sceneInteractor.currentScene.collect { currentScene ->
when (currentScene) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d523bc1867c2..48cf7a83c324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -48,6 +48,9 @@ import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
+import com.android.systemui.statusbar.notification.shelf.NotificationShelfBackgroundView;
+import com.android.systemui.statusbar.notification.shelf.NotificationShelfIconContainer;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -76,7 +79,11 @@ public class NotificationShelf extends ActivatableNotificationView {
private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
- private NotificationIconContainer mShelfIcons;
+ @VisibleForTesting
+ public NotificationShelfIconContainer mShelfIcons;
+ // This field hides mBackgroundNormal from super class for short-shelf alignment
+ @VisibleForTesting
+ public NotificationShelfBackgroundView mBackgroundNormal;
private boolean mHideBackground;
private int mStatusBarHeight;
private boolean mEnableNotificationClipping;
@@ -116,6 +123,8 @@ public class NotificationShelf extends ActivatableNotificationView {
mShelfIcons.setClipChildren(false);
mShelfIcons.setClipToPadding(false);
+ mBackgroundNormal = (NotificationShelfBackgroundView) super.mBackgroundNormal;
+
setClipToActualHeight(false);
setClipChildren(false);
setClipToPadding(false);
@@ -268,19 +277,37 @@ public class NotificationShelf extends ActivatableNotificationView {
}
}
- private void setActualWidth(float actualWidth) {
+ /**
+ * Set the actual width of the shelf, this will only differ from width for short shelves.
+ */
+ @VisibleForTesting
+ public void setActualWidth(float actualWidth) {
setBackgroundWidth((int) actualWidth);
if (mShelfIcons != null) {
+ mShelfIcons.setAlignToEnd(isAlignedToEnd());
mShelfIcons.setActualLayoutWidth((int) actualWidth);
}
mActualWidth = actualWidth;
}
@Override
+ public void setBackgroundWidth(int width) {
+ super.setBackgroundWidth(width);
+ if (!NotificationMinimalism.isEnabled()) {
+ return;
+ }
+ if (mBackgroundNormal != null) {
+ mBackgroundNormal.setAlignToEnd(isAlignedToEnd());
+ }
+ }
+
+ @Override
public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
super.getBoundsOnScreen(outRect, clipToParent);
final int actualWidth = getActualWidth();
- if (isLayoutRtl()) {
+ final boolean alignedToRight = NotificationMinimalism.isEnabled() ? isAlignedToRight() :
+ isLayoutRtl();
+ if (alignedToRight) {
outRect.left = outRect.right - actualWidth;
} else {
outRect.right = outRect.left + actualWidth;
@@ -326,11 +353,17 @@ public class NotificationShelf extends ActivatableNotificationView {
*/
@Override
public boolean pointInView(float localX, float localY, float slop) {
- final float containerWidth = getWidth();
- final float shelfWidth = getActualWidth();
+ final float left, right;
- final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
- final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+ if (NotificationMinimalism.isEnabled()) {
+ left = getShelfLeftBound();
+ right = getShelfRightBound();
+ } else {
+ final float containerWidth = getWidth();
+ final float shelfWidth = getActualWidth();
+ left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+ right = isLayoutRtl() ? containerWidth : shelfWidth;
+ }
final float top = mClipTopAmount;
final float bottom = getActualHeight();
@@ -339,10 +372,53 @@ public class NotificationShelf extends ActivatableNotificationView {
&& isYInView(localY, slop, top, bottom);
}
+ /**
+ * @return The left boundary of the shelf.
+ */
+ @VisibleForTesting
+ public float getShelfLeftBound() {
+ if (isAlignedToRight()) {
+ return getWidth() - getActualWidth();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * @return The right boundary of the shelf.
+ */
+ @VisibleForTesting
+ public float getShelfRightBound() {
+ if (isAlignedToRight()) {
+ return getWidth();
+ } else {
+ return getActualWidth();
+ }
+ }
+
+ @VisibleForTesting
+ public boolean isAlignedToRight() {
+ return isAlignedToEnd() ^ isLayoutRtl();
+ }
+
+ /**
+ * When notification minimalism is on, on split shade, we want the notification shelf to align
+ * to the layout end (right for LTR; left for RTL).
+ * @return whether to align with the minimalism split shade style
+ */
+ @VisibleForTesting
+ public boolean isAlignedToEnd() {
+ if (!NotificationMinimalism.isEnabled()) {
+ return false;
+ }
+ return mAmbientState.getUseSplitShade();
+ }
+
@Override
public void updateBackgroundColors() {
super.updateBackgroundColors();
ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance();
+
if (colorUpdateLogger != null) {
colorUpdateLogger.logEvent("Shelf.updateBackgroundColors()",
"normalBgColor=" + hexColorString(getNormalBgColor())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
index 85c67f5b55a2..4e68bee295fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
@@ -41,14 +41,14 @@ import kotlinx.coroutines.flow.stateIn
class MediaControlChipInteractor
@Inject
constructor(
- @Background private val applicationScope: CoroutineScope,
+ @Background private val backgroundScope: CoroutineScope,
mediaFilterRepository: MediaFilterRepository,
) {
private val currentMediaControls: StateFlow<List<MediaCommonModel.MediaControl>> =
mediaFilterRepository.currentMedia
.map { mediaList -> mediaList.filterIsInstance<MediaCommonModel.MediaControl>() }
.stateIn(
- scope = applicationScope,
+ scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue = emptyList(),
)
@@ -64,7 +64,7 @@ constructor(
?.toMediaControlChipModel()
}
.stateIn(
- scope = applicationScope,
+ scope = backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
new file mode 100644
index 000000000000..3e854b4dbaf8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 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.statusbar.featurepods.media.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
+import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * [StatusBarPopupChipViewModel] for a media control chip in the status bar. This view model is
+ * responsible for converting the [MediaControlChipModel] to a [PopupChipModel] that can be used to
+ * display a media control chip.
+ */
+@SysUISingleton
+class MediaControlChipViewModel
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Application private val applicationContext: Context,
+ mediaControlChipInteractor: MediaControlChipInteractor,
+) : StatusBarPopupChipViewModel {
+
+ /**
+ * A [StateFlow] of the current [PopupChipModel]. This flow emits a new [PopupChipModel]
+ * whenever the underlying [MediaControlChipModel] changes.
+ */
+ override val chip: StateFlow<PopupChipModel> =
+ mediaControlChipInteractor.mediaControlModel
+ .map { mediaControlModel -> toPopupChipModel(mediaControlModel, applicationContext) }
+ .stateIn(
+ backgroundScope,
+ SharingStarted.WhileSubscribed(),
+ PopupChipModel.Hidden(PopupChipId.MediaControl),
+ )
+}
+
+private fun toPopupChipModel(model: MediaControlChipModel?, context: Context): PopupChipModel {
+ if (model == null || model.songName.isNullOrEmpty()) {
+ return PopupChipModel.Hidden(PopupChipId.MediaControl)
+ }
+
+ val contentDescription = model.appName?.let { ContentDescription.Loaded(description = it) }
+ return PopupChipModel.Shown(
+ chipId = PopupChipId.MediaControl,
+ icon =
+ model.appIcon?.loadDrawable(context)?.let {
+ Icon.Loaded(drawable = it, contentDescription = contentDescription)
+ }
+ ?: Icon.Resource(
+ res = com.android.internal.R.drawable.ic_audio_media,
+ contentDescription = contentDescription,
+ ),
+ chipText = model.songName.toString(),
+ // TODO(b/385202114): Show a popup containing the media carousal when the chip is toggled.
+ onToggle = {},
+ // TODO(b/385202193): Add support for clicking on the icon on a media chip.
+ onIconPressed = {},
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
index 1663aebd7287..0a6c4d0fd14f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
@@ -23,7 +23,7 @@ import com.android.systemui.common.shared.model.Icon
* displaying its popup at a time.
*/
sealed class PopupChipId(val value: String) {
- data object MediaControls : PopupChipId("MediaControls")
+ data object MediaControl : PopupChipId("MediaControl")
}
/** Model for individual status bar popup chips. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
new file mode 100644
index 000000000000..56bbd74af1c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.statusbar.featurepods.popups.ui.compose
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+
+/** Container view that holds all right hand side chips in the status bar. */
+@Composable
+fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Modifier = Modifier) {
+ // TODO(b/385353140): Add padding and spacing for this container according to UX specs.
+ Box {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ // TODO(b/385352859): Show `StatusBarPopupChip` here instead of `Text` once it is ready.
+ chips.forEach { chip -> Text(text = chip.chipText) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
index b390f29b166c..caa8e6cc02c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
@@ -18,13 +18,16 @@ package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.MediaControlChipViewModel
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -33,16 +36,29 @@ import kotlinx.coroutines.flow.stateIn
* PopupChipModels.
*/
@SysUISingleton
-class StatusBarPopupChipsViewModel @Inject constructor(@Background scope: CoroutineScope) {
+class StatusBarPopupChipsViewModel
+@Inject
+constructor(
+ @Background scope: CoroutineScope,
+ mediaControlChipViewModel: MediaControlChipViewModel,
+) {
private data class PopupChipBundle(
- val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControls)
+ val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControl)
)
- private val incomingPopupChipBundle: Flow<PopupChipBundle?> =
- flowOf(null).stateIn(scope, SharingStarted.Lazily, PopupChipBundle())
+ private val incomingPopupChipBundle: StateFlow<PopupChipBundle?> =
+ mediaControlChipViewModel.chip
+ .map { chip -> PopupChipBundle(media = chip) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), PopupChipBundle())
- val popupChips: Flow<List<PopupChipModel>> =
- incomingPopupChipBundle
- .map { _ -> listOf(null).filterIsInstance<PopupChipModel.Shown>() }
- .stateIn(scope, SharingStarted.Lazily, emptyList())
+ val shownPopupChips: StateFlow<List<PopupChipModel.Shown>> =
+ if (StatusBarPopupChips.isEnabled) {
+ incomingPopupChipBundle
+ .map { bundle ->
+ listOfNotNull(bundle?.media).filterIsInstance<PopupChipModel.Shown>()
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+ } else {
+ MutableStateFlow(emptyList<PopupChipModel.Shown>()).asStateFlow()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index e440d2728263..dd3a9c9dcf21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -169,12 +169,12 @@ public class NotificationBackgroundView extends View implements Dumpable,
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- final boolean isRtl = isLayoutRtl();
+ final boolean alignedToRight = isAlignedToRight();
final int width = getWidth();
final int actualWidth = getActualWidth();
- int left = isRtl ? width - actualWidth : 0;
- int right = isRtl ? width : actualWidth;
+ int left = alignedToRight ? width - actualWidth : 0;
+ int right = alignedToRight ? width : actualWidth;
if (mExpandAnimationRunning) {
// Horizontally center this background view inside of the container
@@ -185,6 +185,15 @@ public class NotificationBackgroundView extends View implements Dumpable,
return new Rect(left, top, right, bottom);
}
+ /**
+ * @return Whether the background view should be right-aligned. This only matters if the
+ * actualWidth is different than the full (measured) width. In other words, this is used to
+ * define the short-shelf alignment.
+ */
+ protected boolean isAlignedToRight() {
+ return isLayoutRtl();
+ }
+
private void draw(Canvas canvas, Drawable drawable) {
NotificationAddXOnHoverToDismiss.assertInLegacyMode();
@@ -196,12 +205,13 @@ public class NotificationBackgroundView extends View implements Dumpable,
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- final boolean isRtl = isLayoutRtl();
+
+ final boolean alignedToRight = isAlignedToRight();
final int width = getWidth();
final int actualWidth = getActualWidth();
- int left = isRtl ? width - actualWidth : 0;
- int right = isRtl ? width : actualWidth;
+ int left = alignedToRight ? width - actualWidth : 0;
+ int right = alignedToRight ? width : actualWidth;
if (mExpandAnimationRunning) {
// Horizontally center this background view inside of the container
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfBackgroundView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfBackgroundView.kt
new file mode 100644
index 000000000000..d7eea0190e2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfBackgroundView.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.shelf
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.statusbar.notification.row.NotificationBackgroundView
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
+
+/** The background view for the NotificationShelf. */
+class NotificationShelfBackgroundView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
+ NotificationBackgroundView(context, attrs) {
+
+ /** Whether the notification shelf is aligned to end, need to keep persistent with the shelf. */
+ var alignToEnd = false
+
+ /** @return whether the alignment of the notification shelf is right. */
+ @VisibleForTesting
+ public override fun isAlignedToRight(): Boolean {
+ if (!NotificationMinimalism.isEnabled) {
+ return super.isAlignedToRight()
+ }
+ return alignToEnd xor isLayoutRtl
+ }
+
+ override fun toDumpString(): String {
+ return super.toDumpString() + " alignToEnd=" + alignToEnd
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfIconContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfIconContainer.kt
new file mode 100644
index 000000000000..64d165402759
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/NotificationShelfIconContainer.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 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.statusbar.notification.shelf
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import kotlin.math.max
+
+/** The NotificationIconContainer for the NotificationShelf. */
+class NotificationShelfIconContainer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
+ NotificationIconContainer(context, attrs) {
+
+ /** Whether the notification shelf is aligned to end. */
+ var alignToEnd = false
+
+ /**
+ * @return The left boundary (not the RTL compatible start) of the area that icons can be added.
+ */
+ override fun getLeftBound(): Float {
+ if (!NotificationMinimalism.isEnabled) {
+ return super.getLeftBound()
+ }
+
+ if (isAlignedToRight) {
+ return (max(width - actualWidth, 0) + actualPaddingStart)
+ }
+ return actualPaddingStart
+ }
+
+ /**
+ * @return The right boundary (not the RTL compatible end) of the area that icons can be added.
+ */
+ override fun getRightBound(): Float {
+ if (!NotificationMinimalism.isEnabled) {
+ return super.getRightBound()
+ }
+
+ if (isAlignedToRight) {
+ return width - actualPaddingEnd
+ }
+ return actualWidth - actualPaddingEnd
+ }
+
+ /**
+ * For RTL, the icons' x positions should be mirrored around the middle of the shelf so that the
+ * icons are also added to the shelf from right to left. This function should only be called
+ * when RTL.
+ */
+ override fun getRtlIconTranslationX(iconState: IconState, iconView: View): Float {
+ if (!NotificationMinimalism.isEnabled) {
+ return super.getRtlIconTranslationX(iconState, iconView)
+ }
+
+ if (!isLayoutRtl) {
+ return iconState.xTranslation
+ }
+
+ if (isAlignedToRight) {
+ return width * 2 - actualWidth - iconState.xTranslation - iconView.width
+ }
+ return actualWidth - iconState.xTranslation - iconView.width
+ }
+
+ private val isAlignedToRight: Boolean
+ get() {
+ if (!NotificationMinimalism.isEnabled) {
+ return isLayoutRtl
+ }
+ return alignToEnd xor isLayoutRtl
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index ecd62bd6943b..c396512ce3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -198,7 +198,7 @@ public class NotificationIconContainer extends ViewGroup {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
- canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint);
+ canvas.drawRect(getActualPaddingStart(), 0, getRightBound(), getHeight(), paint);
if (DEBUG_OVERFLOW) {
if (mLastVisibleIconState == null) {
@@ -469,11 +469,11 @@ public class NotificationIconContainer extends ViewGroup {
* If this is not a whole number, the fraction means by how much the icon is appearing.
*/
public void calculateIconXTranslations() {
- float translationX = getActualPaddingStart();
+ float translationX = getLeftBound();
int firstOverflowIndex = -1;
int childCount = getChildCount();
int maxVisibleIcons = mMaxIcons;
- float layoutEnd = getLayoutEnd();
+ float layoutRight = getRightBound();
mVisualOverflowStart = 0;
mFirstVisibleIconState = null;
for (int i = 0; i < childCount; i++) {
@@ -495,7 +495,7 @@ public class NotificationIconContainer extends ViewGroup {
final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex,
iconState.iconAppearAmount, maxVisibleIcons);
final boolean isOverflowing = forceOverflow || isOverflowing(
- /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize);
+ /* isLastChild= */ i == childCount - 1, translationX, layoutRight, mIconSize);
// First icon to overflow.
if (firstOverflowIndex == -1 && isOverflowing) {
@@ -536,8 +536,7 @@ public class NotificationIconContainer extends ViewGroup {
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
- iconState.setXTranslation(
- getWidth() - iconState.getXTranslation() - view.getWidth());
+ iconState.setXTranslation(getRtlIconTranslationX(iconState, view));
}
}
if (mIsolatedIcon != null) {
@@ -553,6 +552,11 @@ public class NotificationIconContainer extends ViewGroup {
}
}
+ /** We need this to keep icons ordered from right to left when RTL. */
+ protected float getRtlIconTranslationX(IconState iconState, View iconView) {
+ return getWidth() - iconState.getXTranslation() - iconView.getWidth();
+ }
+
private float getDrawingScale(View view) {
return mUseIncreasedIconScale && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
@@ -563,11 +567,21 @@ public class NotificationIconContainer extends ViewGroup {
mUseIncreasedIconScale = useIncreasedIconScale;
}
- private float getLayoutEnd() {
+ /**
+ * @return The right boundary (not the RTL compatible end) of the area that icons can be added.
+ */
+ protected float getRightBound() {
return getActualWidth() - getActualPaddingEnd();
}
- private float getActualPaddingEnd() {
+ /**
+ * @return The left boundary (not the RTL compatible start) of the area that icons can be added.
+ */
+ protected float getLeftBound() {
+ return getActualPaddingStart();
+ }
+
+ protected float getActualPaddingEnd() {
if (mActualPaddingEnd == NO_VALUE) {
return getPaddingEnd();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index ebf439161a9d..c3299bbd40e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.composable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.LinearLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@@ -28,13 +29,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.keyguard.AlphaOptimizedLinearLayout
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
+import com.android.systemui.statusbar.featurepods.popups.ui.compose.StatusBarPopupChipsContainer
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.PhoneStatusBarView
@@ -172,6 +177,35 @@ fun StatusBarRoot(
R.id.notificationIcons
)
+ // Add a composable container for `StatusBarPopupChip`s
+ if (StatusBarPopupChips.isEnabled) {
+ val endSideContent =
+ phoneStatusBarView.requireViewById<AlphaOptimizedLinearLayout>(
+ R.id.status_bar_end_side_content
+ )
+
+ val composeView =
+ ComposeView(context).apply {
+ layoutParams =
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ )
+
+ setViewCompositionStrategy(
+ ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
+ )
+
+ setContent {
+ val chips =
+ statusBarViewModel.statusBarPopupChips
+ .collectAsStateWithLifecycle()
+ StatusBarPopupChipsContainer(chips = chips.value)
+ }
+ }
+ endSideContent.addView(composeView, 0)
+ }
+
scope.launch {
notificationIconsBinder.bindWhileAttached(
notificationIconContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 7f9a80b2e62f..dcfbc5d6432d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -42,6 +42,8 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
@@ -99,6 +101,9 @@ interface HomeStatusBarViewModel {
/** View model for the carrier name that may show in the status bar based on carrier config */
val operatorNameViewModel: StatusBarOperatorNameViewModel
+ /** The popup chips that should be shown on the right-hand side of the status bar. */
+ val statusBarPopupChips: StateFlow<List<PopupChipModel.Shown>>
+
/**
* True if the current scene can show the home status bar (aka this status bar), and false if
* the current scene should never show the home status bar.
@@ -170,6 +175,7 @@ constructor(
sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
shadeInteractor: ShadeInteractor,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+ statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
animations: SystemStatusEventAnimationInteractor,
@Application coroutineScope: CoroutineScope,
) : HomeStatusBarViewModel {
@@ -188,6 +194,8 @@ constructor(
override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
+ override val statusBarPopupChips = statusBarPopupChipsViewModel.shownPopupChips
+
override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> =
combine(
sceneInteractor.currentScene,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
index fbf7072cc0a0..a6c066500054 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
@@ -31,6 +31,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker
import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity
import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
import dagger.Binds
import dagger.Module
@@ -53,7 +54,11 @@ interface TouchpadTutorialModule {
backGestureScreenViewModel: BackGestureScreenViewModel,
homeGestureScreenViewModel: HomeGestureScreenViewModel,
): TouchpadTutorialScreensProvider {
- return ScreensProvider(backGestureScreenViewModel, homeGestureScreenViewModel)
+ return ScreensProvider(
+ backGestureScreenViewModel,
+ homeGestureScreenViewModel,
+ EasterEggGestureViewModel(),
+ )
}
@SysUISingleton
@@ -74,14 +79,25 @@ interface TouchpadTutorialModule {
private class ScreensProvider(
val backGestureScreenViewModel: BackGestureScreenViewModel,
val homeGestureScreenViewModel: HomeGestureScreenViewModel,
+ val easterEggGestureViewModel: EasterEggGestureViewModel,
) : TouchpadTutorialScreensProvider {
@Composable
override fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
- BackGestureTutorialScreen(backGestureScreenViewModel, onDoneButtonClicked, onBack)
+ BackGestureTutorialScreen(
+ backGestureScreenViewModel,
+ easterEggGestureViewModel,
+ onDoneButtonClicked,
+ onBack,
+ )
}
@Composable
override fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
- HomeGestureTutorialScreen(homeGestureScreenViewModel, onDoneButtonClicked, onBack)
+ HomeGestureTutorialScreen(
+ homeGestureScreenViewModel,
+ easterEggGestureViewModel,
+ onDoneButtonClicked,
+ onBack,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 804a764b5349..ae32b7a6175c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -25,10 +25,12 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenCon
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
@Composable
fun BackGestureTutorialScreen(
viewModel: BackGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -49,9 +51,12 @@ fun BackGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 5dcd788ea4fd..4f1f40dc4c05 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -23,11 +23,13 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
@Composable
fun HomeGestureTutorialScreen(
viewModel: HomeGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -48,9 +50,12 @@ fun HomeGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 7ff838981950..6c9e26c4b7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -23,11 +23,13 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel
@Composable
fun RecentAppsGestureTutorialScreen(
viewModel: RecentAppsGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -49,9 +51,12 @@ fun RecentAppsGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizer.kt
index 7483840d1933..18c490baed3d 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureRecognizer.kt
@@ -17,7 +17,7 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor.Companion.CIRCLES_COUNT_THRESHOLD
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureRecognizer.Companion.CIRCLES_COUNT_THRESHOLD
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.pow
@@ -25,10 +25,12 @@ import kotlin.math.sqrt
/**
* Monitor recognizing easter egg gesture, that is at least [CIRCLES_COUNT_THRESHOLD] circles
- * clockwise within one gesture. It tries to be on the safer side of not triggering gesture if we're
- * not sure if full circle was done.
+ * clockwise within one two-fingers gesture. It tries to be on the safer side of not triggering
+ * gesture if we're not sure if full circle was done.
*/
-class EasterEggGestureMonitor(private val callback: () -> Unit) {
+class EasterEggGestureRecognizer : GestureRecognizer {
+
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
private var last: Point = Point(0f, 0f)
private var cumulativeAngle: Float = 0f
@@ -39,7 +41,16 @@ class EasterEggGestureMonitor(private val callback: () -> Unit) {
private val points = mutableListOf<Point>()
- fun processTouchpadEvent(event: MotionEvent) {
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
+ override fun accept(event: MotionEvent) {
+ if (!isTwoFingerSwipe(event)) return
when (event.action) {
MotionEvent.ACTION_DOWN -> {
reset()
@@ -75,7 +86,7 @@ class EasterEggGestureMonitor(private val callback: () -> Unit) {
// without checking if gesture is circular we can have gesture doing arches back and
// forth that finally reaches full circle angle
if (circleCount >= CIRCLES_COUNT_THRESHOLD && wasGestureCircular(points)) {
- callback()
+ gestureStateChangedCallback(GestureState.Finished)
}
reset()
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt
index dd275bd11d1e..bddeb0b25ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt
@@ -18,33 +18,27 @@ package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.InputDevice
import android.view.MotionEvent
-import java.util.function.Consumer
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.BUTTON_PRIMARY
-/**
- * Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all
- * motion events passed to [onMotionEvent] and will filter touchpad events accordingly
- */
-class TouchpadGestureHandler(
- private val gestureRecognizer: Consumer<MotionEvent>,
- private val easterEggGestureMonitor: EasterEggGestureMonitor,
-) {
+object TouchpadEventsFilter {
- fun onMotionEvent(event: MotionEvent): Boolean {
+ fun isTouchpadAndNonClickEvent(event: MotionEvent): Boolean {
// events from touchpad have SOURCE_MOUSE and not SOURCE_TOUCHPAD because of legacy reasons
val isFromTouchpad =
event.isFromSource(InputDevice.SOURCE_MOUSE) &&
event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER
- val buttonClick =
- event.actionMasked == MotionEvent.ACTION_DOWN &&
- event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
- return if (isFromTouchpad && !buttonClick) {
- if (isTwoFingerSwipe(event)) {
- easterEggGestureMonitor.processTouchpadEvent(event)
- }
- gestureRecognizer.accept(event)
- true
- } else {
- false
- }
+ val isButtonClicked =
+ event.actionMasked == ACTION_DOWN && event.isButtonPressed(BUTTON_PRIMARY)
+ return isFromTouchpad && !isButtonClicked
+ }
+}
+
+fun GestureRecognizer.handleTouchpadMotionEvent(event: MotionEvent): Boolean {
+ return if (TouchpadEventsFilter.isTouchpadAndNonClickEvent(event)) {
+ this.accept(event)
+ true
+ } else {
+ false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 6b4cbab3ae09..cefe382a299c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -39,6 +39,7 @@ import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialS
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
@@ -73,6 +74,7 @@ constructor(
backGestureViewModel,
homeGestureViewModel,
recentAppsGestureViewModel,
+ EasterEggGestureViewModel(),
closeTutorial = ::finishTutorial,
)
}
@@ -105,6 +107,7 @@ fun TouchpadTutorialScreen(
backGestureViewModel: BackGestureScreenViewModel,
homeGestureViewModel: HomeGestureScreenViewModel,
recentAppsGestureViewModel: RecentAppsGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
closeTutorial: () -> Unit,
) {
val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED)
@@ -130,18 +133,21 @@ fun TouchpadTutorialScreen(
BACK_GESTURE ->
BackGestureTutorialScreen(
backGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
HOME_GESTURE ->
HomeGestureTutorialScreen(
homeGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
RECENT_APPS_GESTURE ->
RecentAppsGestureTutorialScreen(
recentAppsGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
index 0154c910be91..93e8d313edcf 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
@@ -22,17 +22,15 @@ import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer
-import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
+import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import com.android.systemui.util.kotlin.pairwiseBy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -40,10 +38,7 @@ class BackGestureScreenViewModel
@Inject
constructor(configurationInteractor: ConfigurationInteractor) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
- private var handler: TouchpadGestureHandler? = null
+ private var recognizer: BackGestureRecognizer? = null
private val distanceThreshold: Flow<Int> =
configurationInteractor
@@ -54,16 +49,15 @@ constructor(configurationInteractor: ConfigurationInteractor) : TouchpadTutorial
override val gestureUiState: Flow<GestureUiState> =
distanceThreshold
.flatMapLatest {
- val recognizer = BackGestureRecognizer(gestureDistanceThresholdPx = it)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
- GestureFlowAdapter(recognizer).gestureStateAsFlow
+ recognizer = BackGestureRecognizer(gestureDistanceThresholdPx = it)
+ GestureFlowAdapter(recognizer!!).gestureStateAsFlow
}
.pairwiseBy(GestureState.NotStarted) { previous, current ->
toGestureUiState(current, previous)
}
override fun handleEvent(event: MotionEvent): Boolean {
- return handler?.onMotionEvent(event) ?: false
+ return recognizer?.handleTouchpadMotionEvent(event) ?: false
}
private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt
new file mode 100644
index 000000000000..69cdab6108ab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 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.touchpad.tutorial.ui.viewmodel
+
+import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureRecognizer
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
+import java.util.function.Consumer
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.receiveAsFlow
+
+class EasterEggGestureViewModel(
+ private val gestureRecognizer: EasterEggGestureRecognizer = EasterEggGestureRecognizer()
+) : Consumer<MotionEvent> {
+
+ private val gestureDone =
+ GestureFlowAdapter(gestureRecognizer).gestureStateAsFlow.filter {
+ it == GestureState.Finished
+ }
+
+ private val easterEggFinished = Channel<Unit>()
+
+ val easterEggTriggered =
+ merge(
+ gestureDone.map { Event.GestureFinished },
+ easterEggFinished.receiveAsFlow().map { Event.StateRestarted },
+ )
+ .map {
+ when (it) {
+ Event.GestureFinished -> true
+ Event.StateRestarted -> false
+ }
+ }
+ .onStart { emit(false) }
+
+ override fun accept(event: MotionEvent) {
+ gestureRecognizer.handleTouchpadMotionEvent(event)
+ }
+
+ fun onEasterEggFinished() {
+ easterEggFinished.trySend(Unit)
+ }
+
+ private sealed interface Event {
+ data object GestureFinished : Event
+
+ data object StateRestarted : Event
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
index 1c865f57b8c7..9a817d810bba 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
@@ -23,17 +23,15 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
-import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker
+import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -47,10 +45,7 @@ constructor(
val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
- private var handler: TouchpadGestureHandler? = null
+ private var recognizer: HomeGestureRecognizer? = null
private val distanceThreshold: Flow<Int> =
configurationInteractor
@@ -67,14 +62,13 @@ constructor(
distanceThreshold
.combine(velocityThreshold, { distance, velocity -> distance to velocity })
.flatMapLatest { (distance, velocity) ->
- val recognizer =
+ recognizer =
HomeGestureRecognizer(
gestureDistanceThresholdPx = distance,
velocityThresholdPxPerMs = velocity,
velocityTracker = velocityTracker,
)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
- GestureFlowAdapter(recognizer).gestureStateAsFlow
+ GestureFlowAdapter(recognizer!!).gestureStateAsFlow
}
.map { toGestureUiState(it) }
@@ -86,6 +80,6 @@ constructor(
)
override fun handleEvent(event: MotionEvent): Boolean {
- return handler?.onMotionEvent(event) ?: false
+ return recognizer?.handleTouchpadMotionEvent(event) ?: false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
index 09947a8b109e..8215078c346d 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
@@ -23,17 +23,15 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
-import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker
+import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -47,10 +45,7 @@ constructor(
private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
- private var handler: TouchpadGestureHandler? = null
+ private var recognizer: RecentAppsGestureRecognizer? = null
private val distanceThreshold: Flow<Int> =
configurationInteractor.onAnyConfigurationChange
@@ -71,14 +66,13 @@ constructor(
distanceThreshold
.combine(velocityThreshold, { distance, velocity -> distance to velocity })
.flatMapLatest { (distance, velocity) ->
- val recognizer =
+ recognizer =
RecentAppsGestureRecognizer(
gestureDistanceThresholdPx = distance,
velocityThresholdPxPerMs = velocity,
velocityTracker = velocityTracker,
)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
- GestureFlowAdapter(recognizer).gestureStateAsFlow
+ GestureFlowAdapter(recognizer!!).gestureStateAsFlow
}
.map { toGestureUiState(it) }
@@ -90,6 +84,6 @@ constructor(
)
override fun handleEvent(event: MotionEvent): Boolean {
- return handler?.onMotionEvent(event) ?: false
+ return recognizer?.handleTouchpadMotionEvent(event) ?: false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
index 500f6a0238c3..31e953d6643c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
@@ -19,15 +19,9 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
interface TouchpadTutorialScreenViewModel {
val gestureUiState: Flow<GestureUiState>
- val easterEggTriggered: MutableStateFlow<Boolean>
-
- fun onEasterEggFinished() {
- easterEggTriggered.value = false
- }
fun handleEvent(event: MotionEvent): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index a7ffcd747a6b..67ffb0602860 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,18 +16,15 @@
package com.android.systemui.volume.dialog.sliders.ui
-import android.animation.Animator
-import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.view.View
-import android.view.animation.DecelerateInterpolator
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
-import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
-import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
-import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -35,53 +32,61 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
-
@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
@Inject
-constructor(
- private val viewModel: VolumeDialogSliderViewModel,
- private val jankListenerFactory: JankListenerFactory,
-) {
+constructor(private val viewModel: VolumeDialogSliderViewModel) {
- fun CoroutineScope.bind(view: View) {
- val sliderView: Slider =
- view.requireViewById<Slider>(R.id.volume_dialog_slider).apply {
- labelBehavior = LabelFormatter.LABEL_GONE
- trackIconActiveColor = trackInactiveTintList
+ private val sliderValueProperty =
+ object : FloatPropertyCompat<Slider>("value") {
+ override fun getValue(slider: Slider): Float = slider.value
+
+ override fun setValue(slider: Slider, value: Float) {
+ slider.value = value
}
+ }
+ private val springForce =
+ SpringForce().apply {
+ stiffness = SpringForce.STIFFNESS_MEDIUM
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ }
+
+ fun CoroutineScope.bind(view: View) {
+ var isInitialUpdate = true
+ val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider)
+ val animation = SpringAnimation(sliderView, sliderValueProperty)
+ animation.spring = springForce
+
sliderView.addOnChangeListener { _, value, fromUser ->
viewModel.setStreamVolume(value.roundToInt(), fromUser)
}
- viewModel.state.onEach { sliderView.setModel(it) }.launchIn(this)
+ viewModel.state
+ .onEach {
+ sliderView.setModel(it, animation, isInitialUpdate)
+ isInitialUpdate = false
+ }
+ .launchIn(this)
}
@SuppressLint("UseCompatLoadingForDrawables")
- private suspend fun Slider.setModel(model: VolumeDialogSliderStateModel) {
+ private fun Slider.setModel(
+ model: VolumeDialogSliderStateModel,
+ animation: SpringAnimation,
+ isInitialUpdate: Boolean,
+ ) {
valueFrom = model.minValue
+ animation.setMinValue(model.minValue)
valueTo = model.maxValue
+ animation.setMaxValue(model.maxValue)
// coerce the current value to the new value range before animating it. This prevents
// animating from the value that is outside of current [valueFrom, valueTo].
value = value.coerceIn(valueFrom, valueTo)
- setValueAnimated(
- model.value,
- jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
- )
- trackIconActiveEnd = context.getDrawable(model.iconRes)
- }
-}
-
-private suspend fun Slider.setValueAnimated(
- newValue: Float,
- jankListener: Animator.AnimatorListener,
-) {
- ObjectAnimator.ofFloat(value, newValue)
- .apply {
- duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS
- interpolator = DecelerateInterpolator()
- addListener(jankListener)
+ setTrackIconActiveStart(model.iconRes)
+ if (isInitialUpdate) {
+ value = model.value
+ } else {
+ animation.animateToFinalPosition(model.value)
}
- .suspendAnimate<Float> { value = it }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 2d5652420ec8..6d8457be1014 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -73,19 +73,22 @@ constructor(
.filterNotNull()
val state: Flow<VolumeDialogSliderStateModel> =
- model.flatMapLatest { streamModel ->
- with(streamModel) {
- volumeDialogSliderIconProvider.getStreamIcon(
- stream = stream,
- level = level,
- levelMin = levelMin,
- levelMax = levelMax,
- isMuted = muted,
- isRoutedToBluetooth = routedToBluetooth,
- )
- }
- .map { icon -> streamModel.toStateModel(icon) }
- }
+ model
+ .flatMapLatest { streamModel ->
+ with(streamModel) {
+ volumeDialogSliderIconProvider.getStreamIcon(
+ stream = stream,
+ level = level,
+ levelMin = levelMin,
+ levelMax = levelMax,
+ isMuted = muted,
+ isRoutedToBluetooth = routedToBluetooth,
+ )
+ }
+ .map { icon -> streamModel.toStateModel(icon) }
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
init {
userVolumeUpdates
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 9b1d86f15c92..52a19e0903e2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.ui.utils
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
import android.view.ViewPropertyAnimator
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringAnimation
@@ -69,17 +70,25 @@ suspend fun ViewPropertyAnimator.suspendAnimate(
@Suppress("UNCHECKED_CAST")
suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) {
suspendCancellableCoroutine { continuation ->
- addListener(
+ val listener =
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) = continuation.resumeIfCan(Unit)
+ override fun onAnimationEnd(animation: Animator) =
+ continuation.resumeIfCan(Unit)
- override fun onAnimationCancel(animation: Animator) = continuation.resumeIfCan(Unit)
- }
- )
- addUpdateListener { onValueChanged(it.animatedValue as T) }
+ override fun onAnimationCancel(animation: Animator) =
+ continuation.resumeIfCan(Unit)
+ }
+ .also(::addListener)
+ val updateListener =
+ AnimatorUpdateListener { onValueChanged(it.animatedValue as T) }
+ .also(::addUpdateListener)
start()
- continuation.invokeOnCancellation { cancel() }
+ continuation.invokeOnCancellation {
+ removeUpdateListener(updateListener)
+ removeListener(listener)
+ cancel()
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index b833750a2c4a..b20678e95a86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -35,7 +35,7 @@ class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(cont
return mediaControllersForToken[token]!!
}
- override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ override suspend fun create(token: SessionToken, looper: Looper): Media3Controller? {
return media3Controller ?: super.create(token, looper)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorKosmos.kt
index 0025ad42ba53..f7e235a9c749 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorKosmos.kt
@@ -17,13 +17,13 @@
package com.android.systemui.statusbar.featurepods.media.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
val Kosmos.mediaControlChipInteractor: MediaControlChipInteractor by
Kosmos.Fixture {
MediaControlChipInteractor(
- applicationScope = applicationCoroutineScope,
+ backgroundScope = backgroundScope,
mediaFilterRepository = mediaFilterRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
new file mode 100644
index 000000000000..7145907a14a8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.statusbar.featurepods.media.ui.viewmodel
+
+import android.content.testableContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
+
+val Kosmos.mediaControlChipViewModel: MediaControlChipViewModel by
+ Kosmos.Fixture {
+ MediaControlChipViewModel(
+ backgroundScope = applicationCoroutineScope,
+ applicationContext = testableContext,
+ mediaControlChipInteractor = mediaControlChipInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
index 62cdc87f980f..93502f365202 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
@@ -18,6 +18,12 @@ package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModel
val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
- Kosmos.Fixture { StatusBarPopupChipsViewModel(testScope.backgroundScope) }
+ Kosmos.Fixture {
+ StatusBarPopupChipsViewModel(
+ testScope.backgroundScope,
+ mediaControlChipViewModel = mediaControlChipViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 924b6b43b49a..b38a723f1fa7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -25,6 +25,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.darkIconInteractor
@@ -48,6 +49,7 @@ var Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
sceneContainerOcclusionInteractor,
shadeInteractor,
ongoingActivityChipsViewModel,
+ statusBarPopupChipsViewModel,
systemStatusEventAnimationInteractor,
applicationCoroutineScope,
)
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 676c6fa4f138..3660607d8764 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -360,13 +360,17 @@ public final class BroadcastHelper {
@Nullable SparseArray<int[]> broadcastAllowList,
@NonNull AndroidPackage pkg,
@NonNull String[] sharedUidPackages,
- @NonNull String reasonForTrace) {
+ @NonNull String reasonForTrace,
+ int callingUidForTrace) {
final boolean isForWholeApp = componentNames.contains(packageName);
+ final String callingPackageNameForTrace = mContext.getPackageManager().getNameForUid(
+ callingUidForTrace);
if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
tracePackageChangedBroadcastEvent(
android.content.pm.Flags.reduceBroadcastsForComponentStateChanges(),
reasonForTrace, packageName, "<implicit>" /* targetPackageName */,
- "whole" /* targetComponent */, componentNames.size());
+ "whole" /* targetComponent */, componentNames.size(),
+ callingPackageNameForTrace);
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
packageUid, reason, userIds, instantUserIds, broadcastAllowList,
null /* targetPackageName */, null /* requiredPermissions */);
@@ -390,7 +394,7 @@ public final class BroadcastHelper {
if (!TextUtils.equals(packageName, "android")) {
tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace, packageName,
"android" /* targetPackageName */, "notExported" /* targetComponent */,
- notExportedComponentNames.size());
+ notExportedComponentNames.size(), callingPackageNameForTrace);
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, "android" /* targetPackageName */,
@@ -401,7 +405,7 @@ public final class BroadcastHelper {
// Second, send the PACKAGE_CHANGED broadcast to the application itself.
tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace, packageName,
packageName /* targetPackageName */, "notExported" /* targetComponent */,
- notExportedComponentNames.size());
+ notExportedComponentNames.size(), callingPackageNameForTrace);
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, packageName /* targetPackageName */,
@@ -415,7 +419,7 @@ public final class BroadcastHelper {
}
tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace, packageName,
sharedPackage /* targetPackageName */, "notExported" /* targetComponent */,
- notExportedComponentNames.size());
+ notExportedComponentNames.size(), callingPackageNameForTrace);
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, sharedPackage /* targetPackageName */,
@@ -427,7 +431,7 @@ public final class BroadcastHelper {
if (!exportedComponentNames.isEmpty()) {
tracePackageChangedBroadcastEvent(true /* applyFlag */, reasonForTrace, packageName,
"<implicit>" /* targetPackageName */, "exported" /* targetComponent */,
- exportedComponentNames.size());
+ exportedComponentNames.size(), callingPackageNameForTrace);
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
exportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, null /* targetPackageName */,
@@ -770,7 +774,8 @@ public final class BroadcastHelper {
dontKillApp,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
pkg.getUid(), null /* reason */,
- "static_shared_library_changed" /* reasonForTrace */);
+ "static_shared_library_changed" /* reasonForTrace */,
+ Process.SYSTEM_UID);
}
}
}
@@ -962,7 +967,8 @@ public final class BroadcastHelper {
@NonNull ArrayList<String> componentNames,
int packageUid,
@NonNull String reason,
- @NonNull String reasonForTrace) {
+ @NonNull String reasonForTrace,
+ int callingUidForTrace) {
PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
Process.SYSTEM_UID);
if (setting == null || setting.getPkg() == null) {
@@ -980,7 +986,7 @@ public final class BroadcastHelper {
mHandler.post(() -> sendPackageChangedBroadcastInternal(
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
instantUserIds, broadcastAllowList, setting.getPkg(),
- sharedUserPackages, reasonForTrace));
+ sharedUserPackages, reasonForTrace, callingUidForTrace));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
}
@@ -1277,7 +1283,7 @@ public final class BroadcastHelper {
private static void tracePackageChangedBroadcastEvent(boolean applyFlag,
@NonNull String reasonForTrace, @Nullable String packageName,
@Nullable String targetPackageName, @Nullable String targetComponent,
- int componentSize) {
+ int componentSize, @Nullable String callingPackageNameForTrace) {
if (!Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
return;
@@ -1291,6 +1297,7 @@ public final class BroadcastHelper {
builder.append(",tpn="); builder.append(targetPackageName);
builder.append(",tc="); builder.append(targetComponent);
builder.append(",cs="); builder.append(componentSize);
+ builder.append(",cpnft="); builder.append(callingPackageNameForTrace);
Trace.instant(Trace.TRACE_TAG_SYSTEM_SERVER, builder.toString());
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b48b39c2edd5..85b92c79403a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2983,7 +2983,7 @@ final class InstallPackageHelper {
}
}
- public void sendPendingBroadcasts(String reasonForTrace) {
+ public void sendPendingBroadcasts(String reasonForTrace, int callingUidForTrace) {
String[] packages;
ArrayList<String>[] components;
int numBroadcasts = 0, numUsers;
@@ -3028,7 +3028,7 @@ final class InstallPackageHelper {
for (int i = 0; i < numBroadcasts; i++) {
mBroadcastHelper.sendPackageChangedBroadcast(snapshot, packages[i],
true /* dontKillApp */, components[i], uids[i], null /* reason */,
- reasonForTrace);
+ reasonForTrace, callingUidForTrace);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 0a067048be42..bc03b10b41b4 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -76,7 +76,7 @@ final class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
case SEND_PENDING_BROADCAST: {
- mPm.sendPendingBroadcasts((String) msg.obj);
+ mPm.sendPendingBroadcasts((String) msg.obj, msg.arg1);
break;
}
case POST_INSTALL: {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a0bbc454c10b..aaa4fdf12411 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3503,7 +3503,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
* if the resetEnabledSettingsOnAppDataCleared is {@code true}.
*/
@GuardedBy("mLock")
- private void resetComponentEnabledSettingsIfNeededLPw(String packageName, int userId) {
+ private void resetComponentEnabledSettingsIfNeededLPw(String packageName, int userId,
+ int callingUid) {
final AndroidPackage pkg = packageName != null ? mPackages.get(packageName) : null;
if (pkg == null || !pkg.isResetEnabledSettingsOnAppDataCleared()) {
return;
@@ -3542,9 +3543,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPendingBroadcasts.addComponents(userId, packageName, updatedComponents);
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(SEND_PENDING_BROADCAST,
- "reset_component_state_changed" /* obj */),
- BROADCAST_DELAY);
+ mHandler.obtainMessage(SEND_PENDING_BROADCAST, callingUid, 0 /* arg2 */,
+ "reset_component_state_changed" /* obj */), BROADCAST_DELAY);
}
}
@@ -3841,8 +3841,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPendingBroadcasts.addComponent(userId, componentPkgName, componentName.getClassName());
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(SEND_PENDING_BROADCAST,
- "component_label_icon_changed" /* obj */), BROADCAST_DELAY);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SEND_PENDING_BROADCAST, callingUid, 0 /* arg2 */,
+ "component_label_icon_changed" /* obj */), BROADCAST_DELAY);
}
}
@@ -4101,8 +4102,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final long broadcastDelay = SystemClock.uptimeMillis() > mServiceStartWithDelay
? BROADCAST_DELAY
: BROADCAST_DELAY_DURING_STARTUP;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(SEND_PENDING_BROADCAST,
- "component_state_changed" /* obj */), broadcastDelay);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SEND_PENDING_BROADCAST, callingUid,
+ 0 /* arg2 */, "component_state_changed" /* obj */),
+ broadcastDelay);
}
}
}
@@ -4121,7 +4124,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
userId, pkgSettings.get(packageName).getAppId());
mBroadcastHelper.sendPackageChangedBroadcast(newSnapshot, packageName,
false /* dontKillApp */, components, packageUid, null /* reason */,
- "component_state_changed" /* reasonForTrace */);
+ "component_state_changed" /* reasonForTrace */, callingUid);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -4349,7 +4352,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
true /* dontKillApp */,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
pkg.getUid(),
- Intent.ACTION_OVERLAY_CHANGED, "overlay_changed" /* reasonForTrace */);
+ Intent.ACTION_OVERLAY_CHANGED, "overlay_changed" /* reasonForTrace */,
+ Process.SYSTEM_UID);
}
}, overlayFilter);
@@ -4847,7 +4851,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId);
synchronized (mLock) {
if (succeeded) {
- resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
+ resetComponentEnabledSettingsIfNeededLPw(packageName, userId,
+ callingUid);
}
}
}
@@ -6357,7 +6362,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@Override
public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) {
final Computer snapshot = snapshotComputer();
- enforceOwnerRights(snapshot, packageName, Binder.getCallingUid());
+ final int callingUid = Binder.getCallingUid();
+ enforceOwnerRights(snapshot, packageName, callingUid);
mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
for (int i = 0; i < mimeTypes.size(); i++) {
if (mimeTypes.get(i).length() > 255) {
@@ -6401,7 +6407,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final int packageUid = UserHandle.getUid(userIds[i], appId);
mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
true /* dontKillApp */, components, packageUid, reason,
- "mime_group_changed" /* reasonForTrace */);
+ "mime_group_changed" /* reasonForTrace */, callingUid);
}
}
});
@@ -8196,8 +8202,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mRemovePackageHelper.cleanUpForMoveInstall(volumeUuid, packageName, fromCodePath);
}
- void sendPendingBroadcasts(String reasonForTrace) {
- mInstallPackageHelper.sendPendingBroadcasts(reasonForTrace);
+ void sendPendingBroadcasts(String reasonForTrace, int callingUidForTrace) {
+ mInstallPackageHelper.sendPendingBroadcasts(reasonForTrace, callingUidForTrace);
}
void handlePackagePostInstall(@NonNull InstallRequest request, boolean launchedForRestore) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b71256d27a14..e4ad56fdc074 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -225,6 +225,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_F
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -1495,7 +1496,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// precede the configuration change from the resize.)
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
- ensureActivityConfiguration(true /* ignoreVisibility */);
+ if (!isPip2ExperimentEnabled()) {
+ // PiP2 should handle sending out the configuration as a part of Shell Transitions.
+ ensureActivityConfiguration(true /* ignoreVisibility */);
+ }
if (inPictureInPictureMode && findMainWindow() == null
&& task.topRunningActivity() == this) {
// Prevent malicious app entering PiP without valid WindowState, which can in turn
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index cfd324830db5..26b7cc67876e 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -34,6 +34,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
import java.io.File;
import java.io.PrintWriter;
@@ -498,50 +499,73 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
}
final TaskFragment currTF = currentActivity.getTaskFragment();
final TaskFragment prevTF = initPrev.getTaskFragment();
- final TaskFragment prevAdjacentTF = prevTF != null
- ? prevTF.getAdjacentTaskFragment() : null;
- if (currTF == prevTF && currTF != null || prevAdjacentTF == null) {
- // Current activity and previous one is in the same task fragment, or
- // previous activity is not in a task fragment, or
- // previous activity's task fragment doesn't adjacent to any others.
+ if (currTF == prevTF || prevTF.asTask() != null || !prevTF.hasAdjacentTaskFragment()) {
+ // Current activity and the initPrev is in the same TaskFragment,
+ // or initPrev activity is a direct child of Task,
+ // or initPrev activity doesn't have an adjacent.
+ // A
+ // B
if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
result.add(initPrev);
}
return;
}
- if (prevAdjacentTF == currTF) {
+ if (currTF.isAdjacentTo(prevTF)) {
// previous activity A is adjacent to current activity B.
// Try to find anyone below previous activityA, which are C and D if exists.
// A | B
// C (| D)
getActivityBelow(initPrev, inTransition, result);
- } else {
- // previous activity C isn't adjacent to current activity A.
- // A
- // B | C
- final Task prevAdjacentTask = prevAdjacentTF.getTask();
- if (prevAdjacentTask == currentTask) {
- final int currentIndex = currTF != null
- ? currentTask.mChildren.indexOf(currTF)
- : currentTask.mChildren.indexOf(currentActivity);
- final int prevAdjacentIndex =
- prevAdjacentTask.mChildren.indexOf(prevAdjacentTF);
- // prevAdjacentTF already above currentActivity
- if (prevAdjacentIndex > currentIndex) {
- return;
- }
+ return;
+ }
+
+ // The initPrev activity has an adjacent that is different from current activity.
+ // A
+ // B | C
+ final int currentIndex = currTF.asTask() != null
+ ? currentTask.mChildren.indexOf(currentActivity)
+ : currentTask.mChildren.indexOf(currTF);
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ final int prevAdjacentIndex = currentTask.mChildren.indexOf(
+ prevTF.getAdjacentTaskFragment());
+ if (prevAdjacentIndex > currentIndex) {
+ // PrevAdjacentTF already above currentActivity
+ return;
}
+ // Add both the one below, and its adjacent.
if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
result.add(initPrev);
}
- // prevAdjacentTF is adjacent to another one
- final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+ final ActivityRecord prevAdjacentActivity = prevTF.getAdjacentTaskFragment()
+ .getTopMostActivity();
if (prevAdjacentActivity != null && (!inTransition
|| isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
result.add(prevAdjacentActivity);
}
+ return;
}
+
+ final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments(
+ prevAdjacentTF -> {
+ final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF);
+ return prevAdjacentIndex > currentIndex;
+ });
+ if (hasAdjacentAboveCurrent) {
+ // PrevAdjacentTF already above currentActivity
+ return;
+ }
+ // Add all adjacent top.
+ if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+ result.add(initPrev);
+ }
+ prevTF.forOtherAdjacentTaskFragments(prevAdjacentTF -> {
+ final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+ if (prevAdjacentActivity != null && (!inTransition
+ || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
+ result.add(prevAdjacentActivity);
+ }
+ });
}
static boolean isInParticipant(ActivityRecord ar,
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 6709e3a72db0..a318c4bf334f 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
@@ -35,6 +36,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -510,6 +512,14 @@ class ActivityStartInterceptor {
}
if (mComponentSpecified) {
+ Slog.w(TAG, "Starting home with component specified, uid=" + mCallingUid);
+ if (mService.isCallerRecents(mCallingUid)
+ || ActivityTaskManagerService.checkPermission(MANAGE_ACTIVITY_TASKS,
+ mCallingPid, mCallingUid) == PERMISSION_GRANTED) {
+ // Allow home component specified from trusted callers.
+ return false;
+ }
+
final ComponentName homeComponent = mIntent.getComponent();
final Intent homeIntent = mService.getHomeIntent();
final ActivityInfo aInfo = mService.mRootWindowContainer.resolveHomeActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0aff1de72cb1..ef6f92317b2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2526,9 +2526,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
task.forAllActivities(r -> {
if (!r.attachedToProcess()) return;
mPipModeChangedActivities.add(r);
- // If we are scheduling pip change, then remove this activity from multi-window
- // change list as the processing of pip change will make sure multi-window changed
- // message is processed in the right order relative to pip changed.
mMultiWindowModeChangedActivities.remove(r);
});
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 7deb6a8232be..dbe0faf942d9 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -270,6 +270,11 @@ class BLASTSyncEngine {
() -> callback.onCommitted(new SurfaceControl.Transaction()));
mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
+ if (mWm.mAnimator.mPendingState == WindowAnimator.PENDING_STATE_NEED_APPLY) {
+ // Applies pending transaction before onTransactionReady to ensure the order with
+ // sync transaction. This is unlikely to happen unless animator thread is slow.
+ mWm.mAnimator.applyPendingTransaction();
+ }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
mListener.onTransactionReady(mSyncId, merged);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -353,6 +358,10 @@ class BLASTSyncEngine {
+ " for non-sync " + wc);
wc.mSyncGroup = null;
}
+ if (mWm.mAnimator.mPendingState == WindowAnimator.PENDING_STATE_HAS_CHANGES
+ && wc.mSyncState != WindowContainer.SYNC_STATE_NONE) {
+ mWm.mAnimator.mPendingState = WindowAnimator.PENDING_STATE_NEED_APPLY;
+ }
if (mReady) {
mWm.mWindowPlacerLocked.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index fc0df645a2db..819395ac60b8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -453,7 +453,7 @@ class BackNavigationController {
outPrevActivities.add(prevActivity);
return true;
}
- if (currTF.getAdjacentTaskFragment() == null) {
+ if (!currTF.hasAdjacentTaskFragment()) {
final TaskFragment nextTF = findNextTaskFragment(currentTask, currTF);
if (isSecondCompanionToFirst(currTF, nextTF)) {
// TF is isStacked, search bottom activity from companion TF.
@@ -476,7 +476,21 @@ class BackNavigationController {
}
} else {
// If adjacent TF has companion to current TF, those two TF will be closed together.
- final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment();
+ final TaskFragment adjacentTF;
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ if (currTF.getAdjacentTaskFragments().size() > 2) {
+ throw new IllegalStateException(
+ "Not yet support 3+ adjacent for non-Task TFs");
+ }
+ final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+ currTF.forOtherAdjacentTaskFragments(tf -> {
+ tmpAdjacent[0] = tf;
+ return true;
+ });
+ adjacentTF = tmpAdjacent[0];
+ } else {
+ adjacentTF = currTF.getAdjacentTaskFragment();
+ }
if (isSecondCompanionToFirst(currTF, adjacentTF)) {
// The two TFs are adjacent (visually displayed side-by-side), search if any
// activity below the lowest one.
@@ -533,29 +547,47 @@ class BackNavigationController {
return;
}
- final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
- if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) {
+ if (!prevTF.hasAdjacentTaskFragment()) {
return;
}
- final ActivityRecord prevActivityAdjacent =
- prevTFAdjacent.getTopNonFinishingActivity();
- if (prevActivityAdjacent != null) {
- outPrevActivities.add(prevActivityAdjacent);
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
+ final ActivityRecord prevActivityAdjacent =
+ prevTFAdjacent.getTopNonFinishingActivity();
+ if (prevActivityAdjacent != null) {
+ outPrevActivities.add(prevActivityAdjacent);
+ }
+ return;
}
+ prevTF.forOtherAdjacentTaskFragments(prevTFAdjacent -> {
+ final ActivityRecord prevActivityAdjacent =
+ prevTFAdjacent.getTopNonFinishingActivity();
+ if (prevActivityAdjacent != null) {
+ outPrevActivities.add(prevActivityAdjacent);
+ }
+ });
}
private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
@NonNull ArrayList<ActivityRecord> outList) {
final TaskFragment mainTF = mainActivity.getTaskFragment();
- if (mainTF == null || mainTF.getAdjacentTaskFragment() == null) {
+ if (mainTF == null || !mainTF.hasAdjacentTaskFragment()) {
return;
}
- final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
- final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
- if (topActivity == null) {
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
+ final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
+ if (topActivity != null) {
+ outList.add(topActivity);
+ }
return;
}
- outList.add(topActivity);
+ mainTF.forOtherAdjacentTaskFragments(adjacentTF -> {
+ final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
+ if (topActivity != null) {
+ outList.add(topActivity);
+ }
+ });
}
private static boolean hasTranslucentActivity(@NonNull ActivityRecord currentActivity,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f9a06e2dd04b..66b77b9d4d2a 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -90,6 +90,7 @@ import com.android.internal.util.Preconditions;
import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
+import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -1650,18 +1651,27 @@ public class BackgroundActivityStartController {
return bas;
}
- TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFragment == null) {
+ if (!taskFragment.hasAdjacentTaskFragment()) {
return bas;
}
- // Check the second fragment.
- topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return bas;
+ // Check the adjacent fragment.
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
+ if (topActivity == null) {
+ return bas;
+ }
+ return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
}
-
- return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
+ final BlockActivityStart[] out = { bas };
+ taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> {
+ final ActivityRecord top = adjacentTaskFragment.getActivity(topOfStackPredicate);
+ if (top != null) {
+ out[0] = checkCrossUidActivitySwitchFromBelow(top, uid, out[0]);
+ }
+ });
+ return out[0];
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5c3fbdfcff0e..09214cd6c553 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -100,6 +100,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
+import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -3288,6 +3289,32 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return new Point(w, h);
}
+ void onDisplayInfoChangeApplied() {
+ if (!enableDisplayContentModeManagement()) {
+ Slog.e(TAG, "ShouldShowSystemDecors shouldn't be updated when the flag is off.");
+ }
+
+ final boolean shouldShow;
+ if (isDefaultDisplay) {
+ shouldShow = true;
+ } else if (isPrivate()) {
+ shouldShow = false;
+ } else {
+ shouldShow = mDisplay.canHostTasks();
+ }
+
+ if (shouldShow == mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)) {
+ return;
+ }
+ mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShow);
+
+ if (shouldShow) {
+ mRootWindowContainer.startSystemDecorations(this, "onDisplayInfoChangeApplied");
+ } else {
+ clearAllTasksOnDisplay(null);
+ }
+ }
+
DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
return null;
@@ -6522,10 +6549,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mRemoving;
}
- void remove() {
- mRemoving = true;
+ private void clearAllTasksOnDisplay(@Nullable Runnable clearTasksCallback) {
Task lastReparentedRootTask;
-
mRootWindowContainer.mTaskSupervisor.beginDeferResume();
try {
lastReparentedRootTask = reduceOnAllTaskDisplayAreas((taskDisplayArea, rootTask) -> {
@@ -6538,10 +6563,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
} finally {
mRootWindowContainer.mTaskSupervisor.endDeferResume();
}
- mRemoved = true;
- if (mContentRecorder != null) {
- mContentRecorder.stopRecording();
+ if (clearTasksCallback != null) {
+ clearTasksCallback.run();
}
// Only update focus/visibility for the last one because there may be many root tasks are
@@ -6549,6 +6573,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (lastReparentedRootTask != null) {
lastReparentedRootTask.resumeNextFocusAfterReparent();
}
+ }
+
+ void remove() {
+ mRemoving = true;
+
+ clearAllTasksOnDisplay(() -> {
+ mRemoved = true;
+
+ if (mContentRecorder != null) {
+ mContentRecorder.stopRecording();
+ }
+ });
+
releaseSelfIfNeeded();
mDisplayPolicy.release();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 63af5c60b8d3..a017a1173d97 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -23,6 +23,8 @@ import static com.android.server.wm.Task.TAG_VISIBILITY;
import android.annotation.Nullable;
import android.util.Slog;
+import com.android.window.flags.Flags;
+
import java.util.ArrayList;
/** Helper class to ensure activities are in the right visible state for a container. */
@@ -110,21 +112,37 @@ class EnsureActivitiesVisibleHelper {
if (adjacentTaskFragments != null && adjacentTaskFragments.contains(
childTaskFragment)) {
- if (!childTaskFragment.isTranslucent(starting)
- && !childTaskFragment.getAdjacentTaskFragment().isTranslucent(
- starting)) {
+ final boolean isTranslucent;
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ isTranslucent = childTaskFragment.isTranslucent(starting)
+ || childTaskFragment.forOtherAdjacentTaskFragments(
+ adjacentTaskFragment -> {
+ return adjacentTaskFragment.isTranslucent(starting);
+ });
+ } else {
+ isTranslucent = childTaskFragment.isTranslucent(starting)
+ || childTaskFragment.getAdjacentTaskFragment()
+ .isTranslucent(starting);
+ }
+ if (!isTranslucent) {
// Everything behind two adjacent TaskFragments are occluded.
mBehindFullyOccludedContainer = true;
}
continue;
}
- final TaskFragment adjacentTaskFrag = childTaskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFrag != null) {
+ if (childTaskFragment.hasAdjacentTaskFragment()) {
if (adjacentTaskFragments == null) {
adjacentTaskFragments = new ArrayList<>();
}
- adjacentTaskFragments.add(adjacentTaskFrag);
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments;
+ childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+ adjacentTfs.add(adjacentTf);
+ });
+ } else {
+ adjacentTaskFragments.add(childTaskFragment.getAdjacentTaskFragment());
+ }
}
} else if (child.asActivityRecord() != null) {
setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 06049530da18..790858d2eec2 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -263,10 +263,9 @@ public class LockTaskController {
// should be finish together in the Task.
if (activity != taskRoot || activity != taskTop) {
final TaskFragment taskFragment = activity.getTaskFragment();
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
if (taskFragment.asTask() != null
|| !taskFragment.isDelayLastActivityRemoval()
- || adjacentTaskFragment == null) {
+ || !taskFragment.hasAdjacentTaskFragment()) {
// Don't block activity from finishing if the TaskFragment don't have any adjacent
// TaskFragment, or it won't finish together with its adjacent TaskFragment.
return false;
@@ -281,7 +280,7 @@ public class LockTaskController {
}
final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing
- && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null;
+ && a != activity && !taskFragment.isAdjacentTo(a.getTaskFragment())) != null;
if (hasOtherActivityInTask) {
// Do not block activity from finishing if there are another running activities
// after the current and adjacent TaskFragments are removed. Note that we don't
@@ -653,6 +652,10 @@ public class LockTaskController {
if (!isSystemCaller) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
+ if (mLockTaskModeTasks.contains(task)) {
+ ProtoLog.w(WM_DEBUG_LOCKTASK, "Already locked.");
+ return;
+ }
// startLockTask() called by app, but app is not part of lock task allowlist. Show
// app pinning request. We will come back here with isSystemCaller true.
ProtoLog.w(WM_DEBUG_LOCKTASK, "Mode default, asking user");
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4f36476c674f..57fe0bb4937e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -46,6 +46,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
+import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -151,6 +152,7 @@ import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.am.UserState;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
@@ -1438,6 +1440,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
: getDefaultTaskDisplayArea();
}
+ // When display content mode management flag is enabled, the task display area is marked as
+ // removed when switching from extended display to mirroring display. We need to restart the
+ // task display area before starting the home.
+ if (enableDisplayContentModeManagement() && taskDisplayArea.isRemoved()) {
+ taskDisplayArea.restart();
+ }
+
Intent homeIntent = null;
ActivityInfo aInfo = null;
if (taskDisplayArea == getDefaultTaskDisplayArea()
@@ -2856,20 +2865,24 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (display == null) {
return;
}
- // Do not start home before booting, or it may accidentally finish booting before it
- // starts. Instead, we expect home activities to be launched when the system is ready
- // (ActivityManagerService#systemReady).
- if (mService.isBooted() || mService.isBooting()) {
- startSystemDecorations(display);
- }
+
+ startSystemDecorations(display, "displayAdded");
+
// Drop any cached DisplayInfos associated with this display id - the values are now
// out of date given this display added event.
mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
- private void startSystemDecorations(final DisplayContent displayContent) {
- startHomeOnDisplay(mCurrentUser, "displayAdded", displayContent.getDisplayId());
+ void startSystemDecorations(final DisplayContent displayContent, String reason) {
+ // Do not start home before booting, or it may accidentally finish booting before it
+ // starts. Instead, we expect home activities to be launched when the system is ready
+ // (ActivityManagerService#systemReady).
+ if (!mService.isBooted() && !mService.isBooting()) {
+ return;
+ }
+
+ startHomeOnDisplay(mCurrentUser, reason, displayContent.getDisplayId());
displayContent.getDisplayPolicy().notifyDisplayReady();
}
@@ -2896,7 +2909,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
synchronized (mService.mGlobalLock) {
final DisplayContent displayContent = getDisplayContent(displayId);
if (displayContent != null) {
- displayContent.requestDisplayUpdate(() -> clearDisplayInfoCaches(displayId));
+ displayContent.requestDisplayUpdate(
+ () -> {
+ clearDisplayInfoCaches(displayId);
+ if (enableDisplayContentModeManagement()) {
+ displayContent.onDisplayInfoChangeApplied();
+ }
+ });
} else {
clearDisplayInfoCaches(displayId);
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9a48d5b8880d..d7b6d96c781d 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -200,6 +200,7 @@ public class SurfaceAnimator {
}
mSnapshot.startAnimation(t, snapshotAnim, type);
}
+ setAnimatorPendingState(t);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -208,6 +209,14 @@ public class SurfaceAnimator {
null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
}
+ /** Indicates that there are surface operations in the pending transaction. */
+ private void setAnimatorPendingState(Transaction t) {
+ if (mService.mAnimator.mPendingState == WindowAnimator.PENDING_STATE_NONE
+ && t == mAnimatable.getPendingTransaction()) {
+ mService.mAnimator.mPendingState = WindowAnimator.PENDING_STATE_HAS_CHANGES;
+ }
+ }
+
/** Returns whether it is currently running an animation. */
boolean isAnimating() {
return mAnimation != null;
@@ -357,6 +366,7 @@ public class SurfaceAnimator {
final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash);
mAnimationFinished = false;
if (scheduleAnim) {
+ setAnimatorPendingState(t);
mService.scheduleAnimationLocked();
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3d0b41ba3a0f..3634bc987d0a 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1887,6 +1887,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return lastReparentedRootTask;
}
+ // TODO(b/385263090): Remove this method
+ void restart() {
+ mRemoved = false;
+ }
+
/**
* Returns the {@link TaskDisplayArea} to which root tasks should be reparented.
*
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index cb6b69072e14..367adc355738 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -518,11 +518,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
}
- // TODO(b/373709676): update usages.
/** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */
@Deprecated
@Nullable
TaskFragment getAdjacentTaskFragment() {
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. "
+ + "Use #getAdjacentTaskFragments instead");
+ }
return mAdjacentTaskFragment;
}
@@ -3523,10 +3526,18 @@ class TaskFragment extends WindowContainer<WindowContainer> {
throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be"
+ " enabled to set more than two TaskFragments adjacent to each other.");
}
- if (taskFragments.size() < 2) {
+ final int size = taskFragments.size();
+ if (size < 2) {
throw new IllegalArgumentException("Adjacent TaskFragments must contain at least"
- + " two TaskFragments, but only " + taskFragments.size()
- + " were provided.");
+ + " two TaskFragments, but only " + size + " were provided.");
+ }
+ if (size > 2) {
+ for (int i = 0; i < size; i++) {
+ if (taskFragments.valueAt(i).asTask() == null) {
+ throw new IllegalArgumentException(
+ "Not yet support 3+ adjacent for non-Task TFs");
+ }
+ }
}
mAdjacentSet = taskFragments;
}
@@ -3604,6 +3615,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return false;
}
+ int size() {
+ return mAdjacentSet.size();
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 49c8559c02a8..790ae1eef0c3 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -26,6 +26,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.IntDef;
import android.content.Context;
import android.os.HandlerExecutor;
import android.os.Trace;
@@ -38,6 +39,8 @@ import com.android.internal.protolog.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -86,6 +89,25 @@ public class WindowAnimator {
private final SurfaceControl.Transaction mTransaction;
+ /** The pending transaction is applied. */
+ static final int PENDING_STATE_NONE = 0;
+ /** There are some (significant) operations set to the pending transaction. */
+ static final int PENDING_STATE_HAS_CHANGES = 1;
+ /** The pending transaction needs to be applied before sending sync transaction to shell. */
+ static final int PENDING_STATE_NEED_APPLY = 2;
+
+ @IntDef(prefix = { "PENDING_STATE_" }, value = {
+ PENDING_STATE_NONE,
+ PENDING_STATE_HAS_CHANGES,
+ PENDING_STATE_NEED_APPLY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PendingState {}
+
+ /** The global state of pending transaction. */
+ @PendingState
+ int mPendingState;
+
WindowAnimator(final WindowManagerService service) {
mService = service;
mContext = service.mContext;
@@ -217,6 +239,7 @@ public class WindowAnimator {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction");
mTransaction.apply();
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ mPendingState = PENDING_STATE_NONE;
mService.mWindowTracing.logState("WindowAnimator");
ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
@@ -296,8 +319,19 @@ public class WindowAnimator {
return mAnimationFrameCallbackScheduled;
}
- Choreographer getChoreographer() {
- return mChoreographer;
+ void applyPendingTransaction() {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyPendingTransaction");
+ mPendingState = PENDING_STATE_NONE;
+ final int numDisplays = mService.mRoot.getChildCount();
+ if (numDisplays == 1) {
+ mService.mRoot.getChildAt(0).getPendingTransaction().apply();
+ } else {
+ for (int i = 0; i < numDisplays; i++) {
+ mTransaction.merge(mService.mRoot.getChildAt(i).getPendingTransaction());
+ }
+ mTransaction.apply();
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 793f18992109..965b22473a2c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9150,7 +9150,7 @@ public class WindowManagerService extends IWindowManager.Stub
// handling the touch-outside event to prevent focus rapid changes back-n-forth.
final boolean shouldDelayTouchForEmbeddedActivity = activity != null
&& activity.isEmbedded()
- && activity.getTaskFragment().getAdjacentTaskFragment() != null;
+ && activity.getTaskFragment().hasAdjacentTaskFragment();
// For cases when there are multiple freeform windows where non-top windows are blocking
// the gesture zones, delay handling the touch-outside event to prevent refocusing the
@@ -9529,21 +9529,41 @@ public class WindowManagerService extends IWindowManager.Stub
return focusedActivity;
}
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- final ActivityRecord adjacentTopActivity =
- adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null;
- if (adjacentTopActivity == null) {
- // Return if no adjacent activity.
+ if (!taskFragment.hasAdjacentTaskFragment()) {
return focusedActivity;
}
- if (adjacentTopActivity.getLastWindowCreateTime()
- < focusedActivity.getLastWindowCreateTime()) {
- // Return if the current focus activity has more recently active window.
- return focusedActivity;
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity();
+ if (adjacentTopActivity == null) {
+ // Return if no adjacent activity.
+ return focusedActivity;
+ }
+
+ if (adjacentTopActivity.getLastWindowCreateTime()
+ < focusedActivity.getLastWindowCreateTime()) {
+ // Return if the current focus activity has more recently active window.
+ return focusedActivity;
+ }
+
+ return adjacentTopActivity;
}
- return adjacentTopActivity;
+ // Find the adjacent activity with more recently active window.
+ final ActivityRecord[] mostRecentActiveActivity = { focusedActivity };
+ final long[] mostRecentActiveTime = { focusedActivity.getLastWindowCreateTime() };
+ taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> {
+ final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity();
+ if (adjacentTopActivity != null) {
+ final long lastWindowCreateTime = adjacentTopActivity.getLastWindowCreateTime();
+ if (lastWindowCreateTime > mostRecentActiveTime[0]) {
+ mostRecentActiveTime[0] = lastWindowCreateTime;
+ mostRecentActiveActivity[0] = adjacentTopActivity;
+ }
+ }
+ });
+ return mostRecentActiveActivity[0];
}
@NonNull
@@ -9592,14 +9612,28 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
final TaskFragment fromFragment = fromWin.getTaskFragment();
- if (fromFragment == null) {
+ if (fromFragment == null || fromFragment.asTask() != null) {
+ // Don't move the focus to another task.
return false;
}
- final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment();
- if (adjacentFragment == null || adjacentFragment.asTask() != null) {
- // Don't move the focus to another task.
+ if (!fromFragment.hasAdjacentTaskFragment()) {
+ // No adjacent window.
return false;
}
+ final TaskFragment adjacentFragment;
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ if (fromFragment.getAdjacentTaskFragments().size() > 2) {
+ throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs");
+ }
+ final TaskFragment[] tmpAdjacent = new TaskFragment[1];
+ fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> {
+ tmpAdjacent[0] = adjacentTF;
+ return true;
+ });
+ adjacentFragment = tmpAdjacent[0];
+ } else {
+ adjacentFragment = fromFragment.getAdjacentTaskFragment();
+ }
if (adjacentFragment.isIsolatedNav()) {
// Don't move the focus if the adjacent TF is isolated navigation.
return false;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index fb197c566b7d..e45ada9438ae 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -80,6 +80,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORG
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -716,6 +717,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
if (forceHiddenForPip) {
wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
+ }
+ if (forceHiddenForPip && !isPip2ExperimentEnabled()) {
// When removing pip, make sure that onStop is sent to the app ahead of
// onPictureInPictureModeChanged.
// See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 0ae7699aeb71..58e4b9177808 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -36,6 +36,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.content.Intent;
+import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
@@ -233,6 +234,7 @@ public class BroadcastHelperTest {
mBroadcastHelper.sendPackageChangedBroadcast(mMockSnapshot,
PACKAGE_CHANGED_TEST_PACKAGE_NAME, true /* dontKillApp */, componentNames,
- UserHandle.USER_SYSTEM, "test" /* reason */, "test" /* reasonForTrace */);
+ UserHandle.USER_SYSTEM, "test" /* reason */, "test" /* reasonForTrace */,
+ Binder.getCallingUid());
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index 76f7e80a3412..0972ea91ea73 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -33,7 +33,6 @@ import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.LongSparseArray;
@@ -72,6 +71,7 @@ public class BlobStoreManagerServiceTest {
private static final String TEST_PKG2 = "com.example2";
private static final String TEST_PKG3 = "com.example3";
+ private static final int TEST_USER_ID = 0;
private static final int TEST_UID1 = 10001;
private static final int TEST_UID2 = 10002;
private static final int TEST_UID3 = 10003;
@@ -98,7 +98,7 @@ public class BlobStoreManagerServiceTest {
mService = new BlobStoreManagerService(mContext, new TestInjector());
mUserSessions = new LongSparseArray<>();
- mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
+ mService.addUserSessionsForTest(mUserSessions, TEST_USER_ID);
}
@After
@@ -360,6 +360,7 @@ public class BlobStoreManagerServiceTest {
return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile,
mock(BlobHandle.class));
}
+
private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
long sessionId, File sessionFile, BlobHandle blobHandle) {
final BlobStoreSession session = mock(BlobStoreSession.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 94a40020cfc8..e3e9cc426bb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -361,12 +361,6 @@ public class ActivityStarterTests extends WindowTestsBase {
return prepareStarter(launchFlags, mockGetRootTask, LAUNCH_MULTIPLE);
}
- private void setupImeWindow() {
- final WindowState imeWindow = createWindow(null, W_INPUT_METHOD,
- "mImeWindow", CURRENT_IME_UID);
- mDisplayContent.mInputMethodWindow = imeWindow;
- }
-
/**
* Creates a {@link ActivityStarter} with default parameters and necessary mocks.
*
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 57aacd36b16b..5cd2a994d81a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -85,6 +85,7 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
import static com.google.common.truth.Truth.assertThat;
@@ -2879,6 +2880,43 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_defaultDisplay() {
+ DisplayContent dc = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
+
+ dc.onDisplayInfoChangeApplied();
+ assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_privateDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.flags = FLAG_PRIVATE;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ spyOn(dc.mDisplay);
+ doReturn(false).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+
+ doReturn(true).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index c016c5ead23c..de0716885214 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -73,7 +73,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
private static final float MID_REFRESH_RATE = 70;
private static final float LOW_REFRESH_RATE = 60;
WindowState createWindow(String name) {
- WindowState window = createWindow(null, TYPE_APPLICATION, name);
+ WindowState window = newWindowBuilder(name, TYPE_APPLICATION).build();
when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
.thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
return window;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index f70dcebce30d..7d59f4872d37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -56,12 +56,13 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testTransparentControlTargetWindowCanShowIme() {
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
- final WindowState appWin = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState popup = createWindow(appWin, TYPE_APPLICATION, "popup");
+ final WindowState appWin = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final WindowState popup = newWindowBuilder("popup", TYPE_APPLICATION).setParent(
+ appWin).build();
popup.mAttrs.format = PixelFormat.TRANSPARENT;
mDisplayContent.setImeLayeringTarget(appWin);
mDisplayContent.updateImeInputAndControlTarget(popup);
@@ -77,11 +78,11 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
@RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme() {
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
- final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState target = newWindowBuilder("app", TYPE_APPLICATION).build();
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.updateImeInputAndControlTarget(target);
performSurfacePlacementAndWaitForWindowAnimator();
@@ -105,14 +106,14 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
@RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_noInitialState() {
- final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState target = newWindowBuilder("app", TYPE_APPLICATION).build();
// Schedule before anything is ready.
mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty());
assertFalse(mImeProvider.isScheduledAndReadyToShowIme());
assertFalse(mImeProvider.isImeShowing());
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
@@ -133,11 +134,11 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
@RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
- final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState target = newWindowBuilder("app", TYPE_APPLICATION).build();
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.updateImeInputAndControlTarget(target);
@@ -166,11 +167,11 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
@RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedSurfacePlacement() {
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
- final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState target = newWindowBuilder("app", TYPE_APPLICATION).build();
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.updateImeInputAndControlTarget(target);
@@ -191,7 +192,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testSetFrozen() {
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisibleAndDrawn(ime);
mImeProvider.setWindowContainer(ime, null, null);
mImeProvider.setServerVisible(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index ee56210e278d..6c5fe1d8551e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -106,8 +106,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
addStatusBar();
addNavigationBar();
- final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ final WindowState win = newWindowBuilder("app", TYPE_APPLICATION).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FREEFORM).setDisplay(
+ mDisplayContent).build();
final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
// The app must not control any system bars.
@@ -120,8 +121,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
addStatusBar();
addNavigationBar();
- final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ final WindowState win = newWindowBuilder("app", TYPE_APPLICATION).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FREEFORM).setDisplay(
+ mDisplayContent).build();
win.setBounds(new Rect());
final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
@@ -136,8 +138,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
addStatusBar();
addNavigationBar();
- final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
- ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ final WindowState win = newWindowBuilder("app", TYPE_APPLICATION).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setWindowingMode(WINDOWING_MODE_FREEFORM).setDisplay(
+ mDisplayContent).build();
win.getTask().setBounds(new Rect(1, 1, 10, 10));
final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
@@ -582,7 +585,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
private WindowState addNavigationBar() {
final Binder owner = new Binder();
- final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState win = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build();
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
win.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
@@ -595,7 +598,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
private WindowState addStatusBar() {
final Binder owner = new Binder();
- final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState win = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
win.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
@@ -607,7 +610,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
private WindowState addWindow(int type, String name) {
- final WindowState win = createWindow(null, type, name);
+ final WindowState win = newWindowBuilder(name, type).build();
mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
return win;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 79967b861ea5..c30aa52b1ef4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,7 +63,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testPostLayout() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
statusBar.setBounds(0, 0, 500, 1000);
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
@@ -81,7 +81,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testPostLayout_invisible() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
statusBar.setBounds(0, 0, 500, 1000);
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
@@ -93,7 +93,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testPostLayout_frameProvider() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
mProvider.setWindowContainer(statusBar,
@@ -108,8 +108,8 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testUpdateControlForTarget() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
// We must not have control or control target before we have the insets source window.
@@ -153,8 +153,8 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testUpdateControlForFakeTarget() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateFakeControlTarget(target);
@@ -166,10 +166,10 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testGetLeash() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
- final WindowState fakeTarget = createWindow(null, TYPE_APPLICATION, "fakeTarget");
- final WindowState otherTarget = createWindow(null, TYPE_APPLICATION, "otherTarget");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
+ final WindowState fakeTarget = newWindowBuilder("fakeTarget", TYPE_APPLICATION).build();
+ final WindowState otherTarget = newWindowBuilder("otherTarget", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
// We must not have control or control target before we have the insets source window,
@@ -208,7 +208,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testUpdateSourceFrame() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
mProvider.setWindowContainer(statusBar, null, null);
statusBar.setBounds(0, 0, 500, 1000);
@@ -238,7 +238,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testUpdateSourceFrameForIme() {
- final WindowState inputMethod = createWindow(null, TYPE_INPUT_METHOD, "inputMethod");
+ final WindowState inputMethod = newWindowBuilder("inputMethod", TYPE_INPUT_METHOD).build();
inputMethod.getFrame().set(new Rect(0, 400, 500, 500));
@@ -262,9 +262,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testUpdateInsetsControlPosition() {
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
- final WindowState ime1 = createWindow(null, TYPE_INPUT_METHOD, "ime1");
+ final WindowState ime1 = newWindowBuilder("ime1", TYPE_INPUT_METHOD).build();
ime1.getFrame().set(new Rect(0, 0, 0, 0));
mImeProvider.setWindowContainer(ime1, null, null);
mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
@@ -272,7 +272,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
mImeProvider.updateInsetsControlPosition(ime1);
assertEquals(new Point(0, 400), mImeProvider.getControl(target).getSurfacePosition());
- final WindowState ime2 = createWindow(null, TYPE_INPUT_METHOD, "ime2");
+ final WindowState ime2 = newWindowBuilder("ime2", TYPE_INPUT_METHOD).build();
ime2.getFrame().set(new Rect(0, 0, 0, 0));
mImeProvider.setWindowContainer(ime2, null, null);
mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
@@ -283,8 +283,8 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testSetRequestedVisibleTypes() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
@@ -295,8 +295,8 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testSetRequestedVisibleTypes_noControl() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState target = newWindowBuilder("target", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
target.setRequestedVisibleTypes(0, statusBars());
@@ -306,7 +306,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
@Test
public void testInsetGeometries() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
mProvider.setWindowContainer(statusBar, null, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 66a66a1e358b..973c8d0a8464 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -76,9 +76,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testStripForDispatch_navBar() {
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState ime = newWindowBuilder("ime", TYPE_APPLICATION).build();
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
@@ -96,9 +96,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testStripForDispatch_pip() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
@@ -113,9 +113,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testStripForDispatch_freeform() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
@@ -129,9 +129,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testStripForDispatch_multiwindow_alwaysOnTop() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
@@ -150,8 +150,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
- final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
- final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+ final WindowState app1 = newWindowBuilder("app1", TYPE_APPLICATION).build();
+ final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).build();
app1.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
@@ -166,7 +166,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
.setVisible(true)
.setFrame(mImeWindow.getFrame());
@@ -181,7 +181,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getRawInsetsState().setSourceVisible(ID_IME, true);
assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
@@ -193,7 +193,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
// This can be the IME z-order target while app cannot be the IME z-order target.
// This is also the only IME control target in this test, so IME won't be invisible caused
// by the control-target change.
- final WindowState base = createWindow(null, TYPE_APPLICATION, "base");
+ final WindowState base = newWindowBuilder("base", TYPE_APPLICATION).build();
mDisplayContent.updateImeInputAndControlTarget(base);
// Make IME and stay visible during the test.
@@ -210,7 +210,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
}
// Send our spy window (app) into the system so that we can detect the invocation.
- final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState win = newWindowBuilder("app", TYPE_APPLICATION).build();
win.setHasSurface(true);
final WindowToken parent = win.mToken;
parent.removeChild(win);
@@ -250,8 +250,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final WindowState child = newWindowBuilder("child", TYPE_APPLICATION).setParent(
+ app).build();
app.mAboveInsetsState.set(getController().getRawInsetsState());
child.mAboveInsetsState.set(getController().getRawInsetsState());
child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
@@ -271,8 +272,9 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final WindowState child = newWindowBuilder("child", TYPE_APPLICATION).setParent(
+ app).build();
app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -288,8 +290,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testImeForDispatch() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
makeWindowVisible(statusBar);
@@ -318,11 +320,11 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testBarControllingWinChanged() {
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
- final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState climateBar = newWindowBuilder("climateBar", TYPE_APPLICATION).build();
+ final WindowState extraNavBar = newWindowBuilder("extraNavBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
@@ -338,8 +340,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testControlRevoked() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
getController().onBarControlTargetChanged(app, null, null, null);
@@ -350,8 +352,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testControlRevoked_animation() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
getController().onBarControlTargetChanged(app, null, null, null);
@@ -362,19 +364,20 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testControlTargetChangedWhileProviderHasNoWindow() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
ID_STATUS_BAR, statusBars());
getController().onBarControlTargetChanged(app, null, null, null);
assertNull(getController().getControlsForDispatch(app));
- provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+ provider.setWindowContainer(newWindowBuilder("statusBar", TYPE_APPLICATION).build(), null,
+ null);
assertNotNull(getController().getControlsForDispatch(app));
}
@Test
public void testTransientVisibilityOfFixedRotationState() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
final InsetsSourceProvider provider = getController()
.getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
provider.setWindowContainer(statusBar, null, null);
@@ -494,7 +497,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testUpdateAboveInsetsState_imeTargetOnScreenBehavior() {
final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent);
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime");
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).setWindowToken(
+ imeToken).build();
final WindowState app = createTestWindow("app");
getController().getOrCreateSourceProvider(ID_IME, ime())
@@ -538,10 +542,10 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testDispatchGlobalInsets() {
- final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
.setWindowContainer(navBar, null, null);
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
app.mAttrs.receiveInsetsIgnoringZOrder = true;
assertNotNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
@@ -580,8 +584,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testHasPendingControls() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_APPLICATION).build();
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
.setWindowContainer(statusBar, null, null);
// No controls dispatched yet.
@@ -598,7 +602,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
/** Creates a window which is associated with ActivityRecord. */
private WindowState createTestWindow(String name) {
- final WindowState win = createWindow(null, TYPE_APPLICATION, name);
+ final WindowState win = newWindowBuilder(name, TYPE_APPLICATION).build();
win.setHasSurface(true);
spyOn(win);
return win;
@@ -606,7 +610,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
/** Creates a non-activity window. */
private WindowState createNonAppWindow(String name) {
- final WindowState win = createWindow(null, LAST_APPLICATION_WINDOW + 1, name);
+ final WindowState win = newWindowBuilder(name, LAST_APPLICATION_WINDOW + 1).build();
win.setHasSurface(true);
spyOn(win);
return win;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index bef4531c9f28..5122aeee588a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -239,6 +239,11 @@ public class LockTaskControllerTest {
verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE);
// THEN screen pinning toast should be shown
verify(mStatusBarService).showPinningEnterExitToast(eq(true /* entering */));
+
+ // WHEN the app calls startLockTaskMode while the Task is already locked
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+ // THEN a pinning request should NOT be shown
+ verify(mStatusBarManagerInternal, never()).showScreenPinningRequest(anyInt(), anyInt());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 73e5f58fa7e0..45436e47e881 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -108,7 +108,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
}
WindowState createWindow(String name) {
- WindowState window = createWindow(null, TYPE_BASE_APPLICATION, name);
+ WindowState window = newWindowBuilder(name, TYPE_BASE_APPLICATION).build();
when(window.getDisplayInfo()).thenReturn(mDisplayInfo);
when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
.thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index c5cbedb9193c..20381ba21758 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -114,8 +114,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
private WindowState createAppOverlayWindow() {
- final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY,
- "testOverlayWindow");
+ final WindowState win = newWindowBuilder("testOverlayWindow",
+ TYPE_APPLICATION_OVERLAY).build();
win.mActivityRecord = null;
win.mHasSurface = true;
return win;
@@ -123,7 +123,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testForwardsShowBackdrop() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
final WindowState overlayWin = createAppOverlayWindow();
try {
@@ -156,7 +156,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testRun() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
final WindowState overlayWin = createAppOverlayWindow();
try {
@@ -200,7 +200,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testCancel() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord,
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
@@ -214,7 +214,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testTimeout() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord,
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
@@ -234,8 +234,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
public void testTimeout_scaled() throws Exception {
try {
mWm.setAnimationScale(2, 5.0f);
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- "testWin");
+ final WindowState win = createTestWindow();
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150),
null, false).mAdapter;
@@ -268,7 +267,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testNotReallyStarted() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mController.createRemoteAnimationRecord(win.mActivityRecord,
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -278,8 +277,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testOneNotStarted() throws Exception {
- final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
- final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
+ final WindowState win1 = newWindowBuilder("testWin1", TYPE_BASE_APPLICATION).build();
+ final WindowState win2 = newWindowBuilder("testWin2", TYPE_BASE_APPLICATION).build();
mController.createRemoteAnimationRecord(win1.mActivityRecord,
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
@@ -306,7 +305,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testRemovedBeforeStarted() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord,
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
@@ -322,7 +321,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testOpeningTaskWithTopFinishingActivity() {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "win");
+ final WindowState win = createTestWindow();
final Task task = win.getTask();
final ActivityRecord topFinishing = new ActivityBuilder(mAtm).setTask(task).build();
// Now the task contains:
@@ -348,7 +347,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testChangeToSmallerSize() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mChangingContainers.add(win.mActivityRecord);
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
@@ -402,7 +401,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testChangeTolargerSize() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mChangingContainers.add(win.mActivityRecord);
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
@@ -456,7 +455,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testChangeToDifferentPosition() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mChangingContainers.add(win.mActivityRecord);
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
@@ -515,7 +514,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
true, mDisplayContent, true /* ownerCanManageAppTokens */);
spyOn(mDisplayContent.mWallpaperController);
doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
try {
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
@@ -548,7 +547,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
true, mDisplayContent, true /* ownerCanManageAppTokens */);
spyOn(mDisplayContent.mWallpaperController);
doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
try {
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
@@ -581,7 +580,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testNonAppIncluded_keygaurdGoingAway() throws Exception {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
// Add overlay window hidden by the keyguard.
final WindowState overlayWin = createAppOverlayWindow();
@@ -631,7 +630,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
true, mDisplayContent, true /* ownerCanManageAppTokens */);
spyOn(mDisplayContent.mWallpaperController);
doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
// Add overlay window hidden by the keyguard.
final WindowState overlayWin = createAppOverlayWindow();
@@ -729,9 +728,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
- final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ final WindowState win = createTestWindow();
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
- final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ final WindowState navBar = newWindowBuilder("NavigationBar", TYPE_NAVIGATION_BAR).build();
mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
spyOn(policy);
@@ -751,4 +750,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
verify(binder, atLeast(0)).asBinder();
verifyNoMoreInteractions(binder);
}
+
+ private WindowState createTestWindow() {
+ return newWindowBuilder("testWin", TYPE_BASE_APPLICATION).build();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6a738ae54dcd..9d9f24cb50f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -673,7 +673,8 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
- final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window");
+ final WindowState window = newWindowBuilder("window", TYPE_BASE_APPLICATION).setWindowToken(
+ mActivity).build();
assertEquals(window, mActivity.findMainWindow());
@@ -3996,8 +3997,8 @@ public class SizeCompatTests extends WindowTestsBase {
resizeDisplay(display, 2200, 2280);
display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate insets, final app bounds are (0, 0, 2200, 2130) - landscape.
- final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
- "navbar");
+ final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
+ mDisplayContent).build();
final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
@@ -4030,8 +4031,8 @@ public class SizeCompatTests extends WindowTestsBase {
resizeDisplay(display, 2200, 2280);
display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
- final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
- "navbar");
+ final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
+ mDisplayContent).build();
final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
@@ -4059,8 +4060,8 @@ public class SizeCompatTests extends WindowTestsBase {
resizeDisplay(dc, 2200, 2280);
dc.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
// Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
- final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
- "navbar");
+ final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
+ mDisplayContent).build();
final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 1c32980aac91..da5210cfa7e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -152,9 +152,9 @@ public class SyncEngineTests extends WindowTestsBase {
final Task task = taskRoot.getTask();
final ActivityRecord translucentTop = new ActivityBuilder(mAtm).setTask(task)
.setActivityTheme(android.R.style.Theme_Translucent).build();
- createWindow(null, TYPE_BASE_APPLICATION, taskRoot, "win");
- final WindowState startingWindow = createWindow(null, TYPE_APPLICATION_STARTING,
- translucentTop, "starting");
+ newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(taskRoot).build();
+ final WindowState startingWindow = newWindowBuilder("starting",
+ TYPE_APPLICATION_STARTING).setWindowToken(translucentTop).build();
startingWindow.mStartingData = new SnapshotStartingData(mWm, null, 0);
task.mSharedStartingData = startingWindow.mStartingData;
task.prepareSync();
@@ -355,7 +355,7 @@ public class SyncEngineTests extends WindowTestsBase {
assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState);
// If the appearance of window won't change after reparenting, its sync state can be kept.
- final WindowState w = createWindow(null, TYPE_BASE_APPLICATION, "win");
+ final WindowState w = newWindowBuilder("win", TYPE_BASE_APPLICATION).build();
parentWC.onRequestedOverrideConfigurationChanged(w.getConfiguration());
w.reparent(botChildWC, POSITION_TOP);
parentWC.prepareSync();
@@ -435,7 +435,7 @@ public class SyncEngineTests extends WindowTestsBase {
@Test
public void testNonBlastMethod() {
- mAppWindow = createWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
+ mAppWindow = newWindowBuilder("mAppWindow", TYPE_BASE_APPLICATION).build();
final BLASTSyncEngine bse = createTestBLASTSyncEngine();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 35a2546fca1a..c0f251e06d17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -52,6 +52,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
@@ -1070,94 +1071,106 @@ public class TaskFragmentTest extends WindowTestsBase {
@EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
- public void testSetAdjacentTaskFragments() {
+ public void testAdjacentSetForTaskFragments() {
final Task task = createTask(mDisplayContent);
final TaskFragment tf0 = createTaskFragmentWithActivity(task);
final TaskFragment tf1 = createTaskFragmentWithActivity(task);
final TaskFragment tf2 = createTaskFragmentWithActivity(task);
- final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
- assertFalse(tf0.hasAdjacentTaskFragment());
-
- tf0.setAdjacentTaskFragments(adjacentTfs);
-
- assertSame(adjacentTfs, tf0.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
- assertTrue(tf0.hasAdjacentTaskFragment());
- assertTrue(tf1.hasAdjacentTaskFragment());
- assertTrue(tf2.hasAdjacentTaskFragment());
-
- final TaskFragment.AdjacentSet adjacentTfs2 = new TaskFragment.AdjacentSet(tf0, tf1);
- tf0.setAdjacentTaskFragments(adjacentTfs2);
-
- assertSame(adjacentTfs2, tf0.getAdjacentTaskFragments());
- assertSame(adjacentTfs2, tf1.getAdjacentTaskFragments());
- assertNull(tf2.getAdjacentTaskFragments());
- assertTrue(tf0.hasAdjacentTaskFragment());
- assertTrue(tf1.hasAdjacentTaskFragment());
- assertFalse(tf2.hasAdjacentTaskFragment());
+
+ // Can have two TFs adjacent,
+ new TaskFragment.AdjacentSet(tf0, tf1);
+
+ // 3+ TFs adjacent is not yet supported.
+ assertThrows(IllegalArgumentException.class,
+ () -> new TaskFragment.AdjacentSet(tf0, tf1, tf2));
}
@EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
- public void testClearAdjacentTaskFragments() {
- final Task task = createTask(mDisplayContent);
- final TaskFragment tf0 = createTaskFragmentWithActivity(task);
- final TaskFragment tf1 = createTaskFragmentWithActivity(task);
- final TaskFragment tf2 = createTaskFragmentWithActivity(task);
- final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
- tf0.setAdjacentTaskFragments(adjacentTfs);
-
- tf0.clearAdjacentTaskFragments();
+ public void testSetAdjacentTaskFragments() {
+ final Task task0 = createTask(mDisplayContent);
+ final Task task1 = createTask(mDisplayContent);
+ final Task task2 = createTask(mDisplayContent);
+ final TaskFragment.AdjacentSet adjTasks = new TaskFragment.AdjacentSet(task0, task1, task2);
+ assertFalse(task0.hasAdjacentTaskFragment());
+
+ task0.setAdjacentTaskFragments(adjTasks);
+
+ assertSame(adjTasks, task0.getAdjacentTaskFragments());
+ assertSame(adjTasks, task1.getAdjacentTaskFragments());
+ assertSame(adjTasks, task2.getAdjacentTaskFragments());
+ assertTrue(task0.hasAdjacentTaskFragment());
+ assertTrue(task1.hasAdjacentTaskFragment());
+ assertTrue(task2.hasAdjacentTaskFragment());
+
+ final TaskFragment.AdjacentSet adjTasks2 = new TaskFragment.AdjacentSet(task0, task1);
+ task0.setAdjacentTaskFragments(adjTasks2);
+
+ assertSame(adjTasks2, task0.getAdjacentTaskFragments());
+ assertSame(adjTasks2, task1.getAdjacentTaskFragments());
+ assertNull(task2.getAdjacentTaskFragments());
+ assertTrue(task0.hasAdjacentTaskFragment());
+ assertTrue(task1.hasAdjacentTaskFragment());
+ assertFalse(task2.hasAdjacentTaskFragment());
+ }
- assertNull(tf0.getAdjacentTaskFragments());
- assertNull(tf1.getAdjacentTaskFragments());
- assertNull(tf2.getAdjacentTaskFragments());
- assertFalse(tf0.hasAdjacentTaskFragment());
- assertFalse(tf1.hasAdjacentTaskFragment());
- assertFalse(tf2.hasAdjacentTaskFragment());
+ @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+ @Test
+ public void testClearAdjacentTaskFragments() {
+ final Task task0 = createTask(mDisplayContent);
+ final Task task1 = createTask(mDisplayContent);
+ final Task task2 = createTask(mDisplayContent);
+ final TaskFragment.AdjacentSet adjTasks = new TaskFragment.AdjacentSet(task0, task1, task2);
+ task0.setAdjacentTaskFragments(adjTasks);
+
+ task0.clearAdjacentTaskFragments();
+
+ assertNull(task0.getAdjacentTaskFragments());
+ assertNull(task1.getAdjacentTaskFragments());
+ assertNull(task2.getAdjacentTaskFragments());
+ assertFalse(task0.hasAdjacentTaskFragment());
+ assertFalse(task1.hasAdjacentTaskFragment());
+ assertFalse(task2.hasAdjacentTaskFragment());
}
@EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testRemoveFromAdjacentTaskFragments() {
- final Task task = createTask(mDisplayContent);
- final TaskFragment tf0 = createTaskFragmentWithActivity(task);
- final TaskFragment tf1 = createTaskFragmentWithActivity(task);
- final TaskFragment tf2 = createTaskFragmentWithActivity(task);
- final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
- tf0.setAdjacentTaskFragments(adjacentTfs);
-
- tf0.removeFromAdjacentTaskFragments();
-
- assertNull(tf0.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
- assertFalse(adjacentTfs.contains(tf0));
- assertTrue(tf1.isAdjacentTo(tf2));
- assertTrue(tf2.isAdjacentTo(tf1));
- assertFalse(tf1.isAdjacentTo(tf0));
- assertFalse(tf0.isAdjacentTo(tf1));
- assertFalse(tf0.isAdjacentTo(tf0));
- assertFalse(tf1.isAdjacentTo(tf1));
+ final Task task0 = createTask(mDisplayContent);
+ final Task task1 = createTask(mDisplayContent);
+ final Task task2 = createTask(mDisplayContent);
+ final TaskFragment.AdjacentSet adjTasks = new TaskFragment.AdjacentSet(task0, task1, task2);
+ task0.setAdjacentTaskFragments(adjTasks);
+
+ task0.removeFromAdjacentTaskFragments();
+
+ assertNull(task0.getAdjacentTaskFragments());
+ assertSame(adjTasks, task1.getAdjacentTaskFragments());
+ assertSame(adjTasks, task2.getAdjacentTaskFragments());
+ assertFalse(adjTasks.contains(task0));
+ assertTrue(task1.isAdjacentTo(task2));
+ assertTrue(task2.isAdjacentTo(task1));
+ assertFalse(task1.isAdjacentTo(task0));
+ assertFalse(task0.isAdjacentTo(task1));
+ assertFalse(task0.isAdjacentTo(task0));
+ assertFalse(task1.isAdjacentTo(task1));
}
@EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
@Test
public void testRemoveFromAdjacentTaskFragmentsWhenRemove() {
- final Task task = createTask(mDisplayContent);
- final TaskFragment tf0 = createTaskFragmentWithActivity(task);
- final TaskFragment tf1 = createTaskFragmentWithActivity(task);
- final TaskFragment tf2 = createTaskFragmentWithActivity(task);
- final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
- tf0.setAdjacentTaskFragments(adjacentTfs);
-
- tf0.removeImmediately();
-
- assertNull(tf0.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
- assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
- assertFalse(adjacentTfs.contains(tf0));
+ final Task task0 = createTask(mDisplayContent);
+ final Task task1 = createTask(mDisplayContent);
+ final Task task2 = createTask(mDisplayContent);
+ final TaskFragment.AdjacentSet adjTasks = new TaskFragment.AdjacentSet(task0, task1, task2);
+ task0.setAdjacentTaskFragments(adjTasks);
+
+ task0.removeImmediately();
+
+ assertNull(task0.getAdjacentTaskFragments());
+ assertSame(adjTasks, task1.getAdjacentTaskFragments());
+ assertSame(adjTasks, task2.getAdjacentTaskFragments());
+ assertFalse(adjTasks.contains(task0));
}
private WindowState createAppWindow(ActivityRecord app, String name) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 78f32c1a4f88..2ee34d3a4b36 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -433,8 +433,8 @@ public class TransitionTests extends WindowTestsBase {
final WallpaperWindowToken wallpaperWindowToken = spy(new WallpaperWindowToken(mWm,
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */));
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = newWindowBuilder("wallpaperWindow",
+ TYPE_WALLPAPER).setWindowToken(wallpaperWindowToken).build();
wallpaperWindowToken.setVisibleRequested(false);
transition.collect(wallpaperWindowToken);
wallpaperWindowToken.setVisibleRequested(true);
@@ -630,8 +630,8 @@ public class TransitionTests extends WindowTestsBase {
// Make DA organized so we can check that they don't get included.
WindowContainer parent = wallpaperWindowToken.getParent();
makeDisplayAreaOrganized(parent, mDisplayContent);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = newWindowBuilder("wallpaperWindow",
+ TYPE_WALLPAPER).setWindowToken(wallpaperWindowToken).build();
wallpaperWindowToken.setVisibleRequested(false);
transition.collect(wallpaperWindowToken);
wallpaperWindowToken.setVisibleRequested(true);
@@ -1114,15 +1114,15 @@ public class TransitionTests extends WindowTestsBase {
// Simulate gesture navigation (non-movable) so it is not seamless.
doReturn(false).when(displayPolicy).navigationBarCanMove();
final Task task = createActivityRecord(mDisplayContent).getTask();
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
- final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
- final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
+ final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build();
+ final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
final WindowToken decorToken = new WindowToken.Builder(mWm, mock(IBinder.class),
TYPE_NAVIGATION_BAR_PANEL).setDisplayContent(mDisplayContent)
.setRoundedCornerOverlay(true).build();
- final WindowState screenDecor =
- createWindow(null, decorToken.windowType, decorToken, "screenDecor");
- final WindowState[] windows = { statusBar, navBar, ime, screenDecor };
+ final WindowState screenDecor = newWindowBuilder("screenDecor",
+ decorToken.windowType).setWindowToken(decorToken).build();
+ final WindowState[] windows = {statusBar, navBar, ime, screenDecor};
makeWindowVisible(windows);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
@@ -1191,7 +1191,7 @@ public class TransitionTests extends WindowTestsBase {
}
private void testShellRotationOpen(TestTransitionPlayer player) {
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
makeWindowVisible(statusBar);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
final ActivityRecord app = createActivityRecord(mDisplayContent);
@@ -1239,7 +1239,7 @@ public class TransitionTests extends WindowTestsBase {
}
private void testFixedRotationOpen(TestTransitionPlayer player) {
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
makeWindowVisible(statusBar);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
final WindowState navBar = createNavBarWithProvidedInsets(mDisplayContent);
@@ -2272,15 +2272,24 @@ public class TransitionTests extends WindowTestsBase {
public void cleanUp(SurfaceControl.Transaction t) {
}
});
+ assertEquals(WindowAnimator.PENDING_STATE_NONE, mWm.mAnimator.mPendingState);
+ app.startAnimation(app.getPendingTransaction(), mock(AnimationAdapter.class),
+ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
+ assertEquals(WindowAnimator.PENDING_STATE_HAS_CHANGES, mWm.mAnimator.mPendingState);
+
final Task task = app.getTask();
transition.collect(task);
+ assertEquals(WindowAnimator.PENDING_STATE_NEED_APPLY, mWm.mAnimator.mPendingState);
final Rect bounds = new Rect(task.getBounds());
Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
bounds.inset(10, 10);
c.windowConfiguration.setBounds(bounds);
task.onRequestedOverrideConfigurationChanged(c);
assertTrue(freezeCalls.contains(task));
- transition.abort();
+
+ transition.start();
+ mWm.mSyncEngine.abort(transition.getSyncId());
+ assertEquals(WindowAnimator.PENDING_STATE_NONE, mWm.mAnimator.mPendingState);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index eb89a9fb20c5..358448e709f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -243,8 +243,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
final WindowState homeWindow = createWallpaperTargetWindow(dc);
- WindowState otherWindow = createWindow(null /* parent */, TYPE_APPLICATION, dc,
- "otherWindow");
+ WindowState otherWindow = newWindowBuilder("otherWindow", TYPE_APPLICATION).setDisplay(
+ dc).build();
dc.mWallpaperController.adjustWallpaperWindows();
@@ -275,7 +275,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
public void testUpdateWallpaperTarget() {
final DisplayContent dc = mDisplayContent;
final WindowState homeWin = createWallpaperTargetWindow(dc);
- final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final WindowState appWin = newWindowBuilder("app", TYPE_BASE_APPLICATION).build();
appWin.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
makeWindowVisible(appWin);
@@ -290,9 +290,9 @@ public class WallpaperControllerTests extends WindowTestsBase {
public void testShowWhenLockedWallpaperTarget() {
final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
wallpaperWindow.mToken.asWallpaperToken().setShowWhenLocked(true);
- final WindowState behind = createWindow(null, TYPE_BASE_APPLICATION, "behind");
- final WindowState topTranslucent = createWindow(null, TYPE_BASE_APPLICATION,
- "topTranslucent");
+ final WindowState behind = newWindowBuilder("behind", TYPE_BASE_APPLICATION).build();
+ final WindowState topTranslucent = newWindowBuilder("topTranslucent",
+ TYPE_BASE_APPLICATION).build();
behind.mAttrs.width = behind.mAttrs.height = topTranslucent.mAttrs.width =
topTranslucent.mAttrs.height = WindowManager.LayoutParams.MATCH_PARENT;
topTranslucent.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -314,8 +314,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
// Only transient-launch transition will make notification shade as last resort target.
// This verifies that regular transition won't choose invisible keyguard as the target.
- final WindowState keyguard = createWindow(null /* parent */,
- WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE, "keyguard");
+ final WindowState keyguard = newWindowBuilder("keyguard",
+ WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE).build();
keyguard.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
registerTestTransitionPlayer();
final Transition transition = wallpaperWindow.mTransitionController.createTransition(
@@ -568,8 +568,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
private WindowState createWallpaperWindow(DisplayContent dc) {
final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
true /* explicit */, dc, true /* ownerCanManageAppTokens */);
- return createWindow(null /* parent */, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ return newWindowBuilder("wallpaperWindow", TYPE_WALLPAPER).setWindowToken(
+ wallpaperWindowToken).build();
}
private WindowState createWallpaperTargetWindow(DisplayContent dc) {
@@ -578,8 +578,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
.build();
homeActivity.setVisibility(true);
- WindowState appWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- homeActivity, "wallpaperTargetWindow");
+ WindowState appWindow = newWindowBuilder("wallpaperTargetWindow",
+ TYPE_BASE_APPLICATION).setWindowToken(homeActivity).build();
appWindow.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
appWindow.mHasSurface = true;
spyOn(appWindow);