summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp13
-rw-r--r--api/OWNERS2
-rwxr-xr-xcmds/am/am.sh5
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java17
-rw-r--r--cmds/uinput/tests/Android.bp14
-rw-r--r--core/java/android/accessibilityservice/OWNERS5
-rw-r--r--core/java/android/app/ActivityOptions.java10
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/os/storage/StorageManager.java266
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java2
-rw-r--r--core/java/android/view/InputEventReceiver.java8
-rw-r--r--core/java/android/view/ViewRootImpl.java19
-rw-r--r--core/java/android/view/accessibility/OWNERS5
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java28
-rw-r--r--core/java/android/window/DesktopModeFlags.java1
-rw-r--r--core/java/android/window/TransitionInfo.java5
-rw-r--r--core/java/android/window/WindowTokenClient.java1
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig23
-rw-r--r--core/java/com/android/internal/accessibility/OWNERS5
-rw-r--r--core/java/com/android/internal/graphics/palette/OWNERS5
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java24
-rw-r--r--core/java/com/android/internal/protolog/WmProtoLogGroups.java2
-rw-r--r--core/res/res/values-watch/config.xml4
-rw-r--r--core/res/res/values/config.xml22
-rw-r--r--core/res/res/values/symbols.xml13
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--graphics/java/android/graphics/Bitmap.java4
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java14
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java36
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt194
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt5
-rw-r--r--libs/hwui/hwui/Bitmap.cpp6
-rw-r--r--libs/hwui/hwui/Bitmap.h8
-rw-r--r--libs/hwui/jni/Bitmap.cpp26
-rw-r--r--libs/hwui/jni/Bitmap.h7
-rw-r--r--libs/hwui/jni/ScopedParcel.cpp10
-rw-r--r--libs/hwui/jni/ScopedParcel.h4
-rw-r--r--media/java/android/media/audiofx/HapticGenerator.java14
-rw-r--r--packages/SettingsLib/Graph/graph.proto2
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt96
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt11
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt54
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt2
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt55
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt64
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java37
-rw-r--r--packages/Shell/src/com/android/shell/BugreportPrefs.java29
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java8
-rw-r--r--packages/Shell/src/com/android/shell/BugreportWarningActivity.java23
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java37
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt12
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt115
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt328
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt99
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt261
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt3
-rw-r--r--packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml20
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml8
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_button.xml25
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/OWNERS5
-rw-r--r--packages/SystemUI/src/com/android/systemui/ailabs/OWNERS1
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/stylus/OWNERS3
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt153
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt243
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt2
-rw-r--r--services/accessibility/OWNERS5
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/OWNERS8
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java247
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java24
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java10
-rw-r--r--services/core/java/com/android/server/display/mode/VotesStatsReporter.java25
-rw-r--r--services/core/java/com/android/server/notification/NotificationChannelExtractor.java2
-rw-r--r--services/core/java/com/android/server/security/OWNERS1
-rw-r--r--services/core/java/com/android/server/storage/ImmutableVolumeInfo.java139
-rw-r--r--services/core/java/com/android/server/storage/StorageSessionController.java30
-rw-r--r--services/core/java/com/android/server/storage/WatchedVolumeInfo.java206
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java48
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java19
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java41
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java15
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java10
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java20
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java17
-rw-r--r--services/core/java/com/android/server/wm/PresentationController.java86
-rw-r--r--services/core/java/com/android/server/wm/Session.java10
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java1
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java22
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java65
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/OWNERS5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/OWNERS6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java180
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java4
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java6
-rw-r--r--telephony/java/com/android/internal/telephony/ISub.aidl6
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/Rotation/AndroidTestTemplate.xml2
-rw-r--r--tools/aapt2/cmd/Command.cpp24
-rw-r--r--tools/aapt2/cmd/Command_test.cpp18
-rw-r--r--tools/aapt2/cmd/Convert.cpp3
-rw-r--r--tools/aapt2/cmd/Convert.h9
-rw-r--r--tools/aapt2/cmd/Link.cpp3
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Optimize.cpp3
-rw-r--r--tools/aapt2/cmd/Optimize.h11
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp2
-rw-r--r--tools/aapt2/format/binary/TableFlattener.h5
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp12
-rw-r--r--tools/aapt2/readme.md2
186 files changed, 3929 insertions, 1519 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 071cc6555be7..e5c059ecbfb7 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -84,7 +84,7 @@ aconfig_declarations_group {
"android.view.inputmethod.flags-aconfig-java",
"android.webkit.flags-aconfig-java",
"android.widget.flags-aconfig-java",
- "android.xr.flags-aconfig-java",
+ "android.xr.flags-aconfig-java-export",
"art_exported_aconfig_flags_lib",
"backstage_power_flags_lib",
"backup_flags_lib",
@@ -989,15 +989,22 @@ java_aconfig_library {
// XR
aconfig_declarations {
name: "android.xr.flags-aconfig",
- package: "android.xr",
container: "system",
+ exportable: true,
+ package: "android.xr",
srcs: ["core/java/android/content/pm/xr.aconfig"],
}
java_aconfig_library {
- name: "android.xr.flags-aconfig-java",
+ name: "android.xr.flags-aconfig-java-export",
aconfig_declarations: "android.xr.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ mode: "exported",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
}
// android.app
diff --git a/api/OWNERS b/api/OWNERS
index 965093c9ab38..f2bcf13d2d2e 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -9,4 +9,4 @@ per-file *.go,go.mod,go.work,go.work.sum = file:platform/build/soong:/OWNERS
per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
# For metalava team to disable lint checks in platform
-per-file Android.bp = aurimas@google.com,emberrose@google.com
+per-file Android.bp = aurimas@google.com
diff --git a/cmds/am/am.sh b/cmds/am/am.sh
index 76ec214cb446..f099be3e26a2 100755
--- a/cmds/am/am.sh
+++ b/cmds/am/am.sh
@@ -1,11 +1,10 @@
#!/system/bin/sh
-# set to top-app process group
-settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
-
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
+ # set to top-app process group for instrument
+ settaskprofile $$ SCHED_SP_TOP_APP >/dev/null 2>&1 || true
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 6310d32515c5..696bc82a9ffc 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,6 +18,7 @@ package com.android.commands.bmgr;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupProgress;
@@ -73,6 +74,8 @@ public class Bmgr {
"Error: Could not access the backup transport. Is the system running?";
private static final String PM_NOT_RUNNING_ERR =
"Error: Could not access the Package Manager. Is the system running?";
+ private static final String INVALID_USER_ID_ERR_TEMPLATE =
+ "Error: Invalid user id (%d).\n";
private String[] mArgs;
private int mNextArg;
@@ -104,6 +107,11 @@ public class Bmgr {
mArgs = args;
mNextArg = 0;
int userId = parseUserId();
+ if (userId < 0) {
+ System.err.printf(INVALID_USER_ID_ERR_TEMPLATE, userId);
+ return;
+ }
+
String op = nextArg();
Slog.v(TAG, "Running " + op + " for user:" + userId);
@@ -955,12 +963,15 @@ public class Bmgr {
private int parseUserId() {
String arg = nextArg();
- if ("--user".equals(arg)) {
- return UserHandle.parseUserArg(nextArg());
- } else {
+ if (!"--user".equals(arg)) {
mNextArg--;
return UserHandle.USER_SYSTEM;
}
+ int userId = UserHandle.parseUserArg(nextArg());
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+ return userId;
}
private static void showUsage() {
diff --git a/cmds/uinput/tests/Android.bp b/cmds/uinput/tests/Android.bp
index e728bd270a46..516de3325f77 100644
--- a/cmds/uinput/tests/Android.bp
+++ b/cmds/uinput/tests/Android.bp
@@ -18,3 +18,17 @@ android_test {
"device-tests",
],
}
+
+android_ravenwood_test {
+ name: "UinputTestsRavenwood",
+ srcs: [
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "frameworks-base-testutils",
+ "platform-test-annotations",
+ "truth",
+ "uinput",
+ ],
+}
diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS
index 1265dfa2c441..dac64f47ba7e 100644
--- a/core/java/android/accessibilityservice/OWNERS
+++ b/core/java/android/accessibilityservice/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
# Android Accessibility Framework owners
include /services/accessibility/OWNERS \ No newline at end of file
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 82c746a8ad4c..b8c20bd97264 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2230,6 +2230,16 @@ public class ActivityOptions extends ComponentOptions {
return mLaunchCookie;
}
+ /**
+ * Set the ability for the current transition/animation to work cross-task.
+ * @param allowTaskOverride true to allow cross-task use, otherwise false.
+ *
+ * @hide
+ */
+ public ActivityOptions setOverrideTaskTransition(boolean allowTaskOverride) {
+ this.mOverrideTaskTransition = allowTaskOverride;
+ return this;
+ }
/** @hide */
public boolean getOverrideTaskTransition() {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index fded88212127..d8919160320a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -641,6 +641,9 @@ public final class DisplayManager {
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
+ * This event is not triggered for refresh rate changes as they can change very often.
+ * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+ *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -839,6 +842,9 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
+ * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+ * instead to subscribe for explicit events of interest
+ *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -847,7 +853,9 @@ public final class DisplayManager {
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED
+ | EVENT_TYPE_DISPLAY_REFRESH_RATE
+ | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed25bd9..339dbf2c2029 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@ public final class DisplayManagerGlobal {
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- // For backward compatibility, a client subscribing to
- // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
- // RR changes
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
}
- if ((eventFlags
- & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if (Flags.displayListenerPerformanceImprovements()) {
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+ if (Flags.displayListenerPerformanceImprovements()) {
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
-
return baseEventMask;
}
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 91ad22f51345..24f8672c1e7c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -22,6 +22,7 @@ import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.PER_USER_RANGE;
@@ -44,6 +45,7 @@ import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
@@ -269,14 +271,15 @@ public class StorageManager {
public static final int FLAG_STORAGE_SDK = IInstalld.FLAG_STORAGE_SDK;
/** {@hide} */
- @IntDef(prefix = "FLAG_STORAGE_", value = {
+ @IntDef(prefix = "FLAG_STORAGE_", value = {
FLAG_STORAGE_DE,
FLAG_STORAGE_CE,
FLAG_STORAGE_EXTERNAL,
FLAG_STORAGE_SDK,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface StorageFlags {}
+ public @interface StorageFlags {
+ }
/** {@hide} */
public static final int FLAG_FOR_WRITE = 1 << 8;
@@ -309,6 +312,44 @@ public class StorageManager {
@GuardedBy("mDelegates")
private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
+ static record VolumeListQuery(int mUserId, String mPackageName, int mFlags) {
+ }
+
+ private static final PropertyInvalidatedCache.QueryHandler<VolumeListQuery, StorageVolume[]>
+ sVolumeListQuery = new PropertyInvalidatedCache.QueryHandler<>() {
+ @androidx.annotation.Nullable
+ @Override
+ public StorageVolume[] apply(@androidx.annotation.NonNull VolumeListQuery query) {
+ final IStorageManager storageManager = IStorageManager.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ if (storageManager == null) {
+ // negative results won't be cached, so we will just try again next time
+ return null;
+ }
+ try {
+ return storageManager.getVolumeList(
+ query.mUserId, query.mPackageName, query.mFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
+
+ // Generally, the userId and packageName parameters stay pretty constant, but flags may change
+ // regularly; we have observed some processes hitting 10+ variations.
+ private static final int VOLUME_LIST_CACHE_MAX = 16;
+
+ private static final PropertyInvalidatedCache<VolumeListQuery, StorageVolume[]>
+ sVolumeListCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM).cacheNulls(false)
+ .api("getVolumeList").maxEntries(VOLUME_LIST_CACHE_MAX), "getVolumeList",
+ sVolumeListQuery);
+
+ /** {@hide} */
+ public static void invalidateVolumeListCache() {
+ sVolumeListCache.invalidateCache();
+ }
+
private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
final Executor mExecutor;
final StorageEventListener mListener;
@@ -395,7 +436,8 @@ public class StorageManager {
private class ObbActionListener extends IObbActionListener.Stub {
@SuppressWarnings("hiding")
- private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
+ private SparseArray<ObbListenerDelegate> mListeners =
+ new SparseArray<ObbListenerDelegate>();
@Override
public void onObbResult(String filename, int nonce, int status) {
@@ -477,10 +519,10 @@ public class StorageManager {
*
* @param looper The {@link android.os.Looper} which events will be received on.
*
- * <p>Applications can get instance of this class by calling
- * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
- * of {@link android.content.Context#STORAGE_SERVICE}.
- *
+ * <p>Applications can get instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)} with an
+ * argument
+ * of {@link android.content.Context#STORAGE_SERVICE}.
* @hide
*/
@UnsupportedAppUsage
@@ -488,15 +530,16 @@ public class StorageManager {
mContext = context;
mResolver = context.getContentResolver();
mLooper = looper;
- mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getServiceOrThrow("mount"));
+ mStorageManager = IStorageManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow("mount"));
mAppOps = mContext.getSystemService(AppOpsManager.class);
}
/**
* Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
- * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
- *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener}
+ * object.
* @hide
*/
@UnsupportedAppUsage
@@ -516,14 +559,14 @@ public class StorageManager {
/**
* Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
- * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
- *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener}
+ * object.
* @hide
*/
@UnsupportedAppUsage
public void unregisterListener(StorageEventListener listener) {
synchronized (mDelegates) {
- for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext(); ) {
final StorageEventListenerDelegate delegate = i.next();
if (delegate.mListener == listener) {
try {
@@ -558,7 +601,8 @@ public class StorageManager {
* {@link StorageManager#getStorageVolumes()} to observe the latest
* value.
*/
- public void onStateChanged(@NonNull StorageVolume volume) { }
+ public void onStateChanged(@NonNull StorageVolume volume) {
+ }
}
/**
@@ -592,7 +636,7 @@ public class StorageManager {
*/
public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
synchronized (mDelegates) {
- for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext(); ) {
final StorageEventListenerDelegate delegate = i.next();
if (delegate.mCallback == callback) {
try {
@@ -628,8 +672,8 @@ public class StorageManager {
/**
* Query if a USB Mass Storage (UMS) host is connected.
- * @return true if UMS host is connected.
*
+ * @return true if UMS host is connected.
* @hide
*/
@Deprecated
@@ -640,8 +684,8 @@ public class StorageManager {
/**
* Query if a USB Mass Storage (UMS) is enabled on the device.
- * @return true if UMS host is enabled.
*
+ * @return true if UMS host is enabled.
* @hide
*/
@Deprecated
@@ -663,11 +707,11 @@ public class StorageManager {
* That is, shared UID applications can attempt to mount any other
* application's OBB that shares its UID.
*
- * @param rawPath the path to the OBB file
- * @param key must be <code>null</code>. Previously, some Android device
- * implementations accepted a non-<code>null</code> key to mount
- * an encrypted OBB file. However, this never worked reliably and
- * is no longer supported.
+ * @param rawPath the path to the OBB file
+ * @param key must be <code>null</code>. Previously, some Android device
+ * implementations accepted a non-<code>null</code> key to mount
+ * an encrypted OBB file. However, this never worked reliably and
+ * is no longer supported.
* @param listener will receive the success or failure of the operation
* @return whether the mount call was successfully queued or not
*/
@@ -739,9 +783,9 @@ public class StorageManager {
* application's OBB that shares its UID.
* <p>
*
- * @param rawPath path to the OBB file
- * @param force whether to kill any programs using this in order to unmount
- * it
+ * @param rawPath path to the OBB file
+ * @param force whether to kill any programs using this in order to unmount
+ * it
* @param listener will receive the success or failure of the operation
* @return whether the unmount call was successfully queued or not
*/
@@ -781,7 +825,7 @@ public class StorageManager {
*
* @param rawPath path to OBB image
* @return absolute path to mounted OBB image data or <code>null</code> if
- * not mounted or exception encountered trying to read status
+ * not mounted or exception encountered trying to read status
*/
public String getMountedObbPath(String rawPath) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
@@ -899,7 +943,7 @@ public class StorageManager {
* {@link #UUID_DEFAULT}.
*
* @throws IOException when the storage device hosting the given path isn't
- * present, or when it doesn't have a valid UUID.
+ * present, or when it doesn't have a valid UUID.
*/
public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException {
Preconditions.checkNotNull(path);
@@ -1172,8 +1216,8 @@ public class StorageManager {
/**
* This is not the API you're looking for.
*
- * @see PackageManager#getPrimaryStorageCurrentVolume()
* @hide
+ * @see PackageManager#getPrimaryStorageCurrentVolume()
*/
public String getPrimaryStorageUuid() {
try {
@@ -1186,8 +1230,8 @@ public class StorageManager {
/**
* This is not the API you're looking for.
*
- * @see PackageManager#movePrimaryStorage(VolumeInfo)
* @hide
+ * @see PackageManager#movePrimaryStorage(VolumeInfo)
*/
public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
try {
@@ -1216,7 +1260,7 @@ public class StorageManager {
// resolve the actual volume name
if (Objects.equals(volumeName, MediaStore.VOLUME_EXTERNAL)) {
try (Cursor c = mContext.getContentResolver().query(uri,
- new String[] { MediaStore.MediaColumns.VOLUME_NAME }, null, null)) {
+ new String[]{MediaStore.MediaColumns.VOLUME_NAME}, null, null)) {
if (c.moveToFirst()) {
volumeName = c.getString(0);
}
@@ -1275,6 +1319,7 @@ public class StorageManager {
/**
* Gets the state of a volume via its mountpoint.
+ *
* @hide
*/
@Deprecated
@@ -1308,7 +1353,7 @@ public class StorageManager {
* Return the list of shared/external storage volumes currently available to
* the calling user and the user it shares media with. Please refer to
* <a href="https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support">
- * multi-user support</a> for more details.
+ * multi-user support</a> for more details.
*
* <p>
* This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
@@ -1353,7 +1398,7 @@ public class StorageManager {
public static Pair<String, Long> getPrimaryStoragePathAndSize() {
return Pair.create(null,
FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
- + Environment.getRootDirectory().getTotalSpace()));
+ + Environment.getRootDirectory().getTotalSpace()));
}
/** {@hide} */
@@ -1389,8 +1434,6 @@ public class StorageManager {
/** {@hide} */
@UnsupportedAppUsage
public static @NonNull StorageVolume[] getVolumeList(int userId, int flags) {
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.getService("mount"));
try {
String packageName = ActivityThread.currentOpPackageName();
if (packageName == null) {
@@ -1406,7 +1449,7 @@ public class StorageManager {
}
packageName = packageNames[0];
}
- return storageManager.getVolumeList(userId, packageName, flags);
+ return sVolumeListCache.query(new VolumeListQuery(userId, packageName, flags));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1414,6 +1457,7 @@ public class StorageManager {
/**
* Returns list of paths for all mountable volumes.
+ *
* @hide
*/
@Deprecated
@@ -1605,7 +1649,7 @@ public class StorageManager {
* <p>
* This is only intended to be called by UserManagerService, as part of creating a user.
*
- * @param userId ID of the user
+ * @param userId ID of the user
* @param ephemeral whether the user is ephemeral
* @throws RuntimeException on error. The user's keys already existing is considered an error.
* @hide
@@ -1711,7 +1755,8 @@ public class StorageManager {
return false;
}
- /** {@hide}
+ /**
+ * {@hide}
* Is this device encrypted?
* <p>
* Note: all devices launching with Android 10 (API level 29) or later are
@@ -1724,8 +1769,10 @@ public class StorageManager {
return RoSystemProperties.CRYPTO_ENCRYPTED;
}
- /** {@hide}
+ /**
+ * {@hide}
* Does this device have file-based encryption (FBE) enabled?
+ *
* @return true if the device has file-based encryption enabled.
*/
public static boolean isFileEncrypted() {
@@ -1759,8 +1806,8 @@ public class StorageManager {
}
/**
- * @deprecated disabled now that FUSE has been replaced by sdcardfs
* @hide
+ * @deprecated disabled now that FUSE has been replaced by sdcardfs
*/
@Deprecated
public static File maybeTranslateEmulatedPathToInternal(File path) {
@@ -1790,6 +1837,7 @@ public class StorageManager {
/**
* Check that given app holds both permission and appop.
+ *
* @hide
*/
public static boolean checkPermissionAndAppOp(Context context, boolean enforce, int pid,
@@ -1800,6 +1848,7 @@ public class StorageManager {
/**
* Check that given app holds both permission and appop but do not noteOp.
+ *
* @hide
*/
public static boolean checkPermissionAndCheckOp(Context context, boolean enforce,
@@ -1810,6 +1859,7 @@ public class StorageManager {
/**
* Check that given app holds both permission and appop.
+ *
* @hide
*/
private static boolean checkPermissionAndAppOp(Context context, boolean enforce, int pid,
@@ -1877,7 +1927,9 @@ public class StorageManager {
// Legacy apps technically have the access granted by this op,
// even when the op is denied
if ((mAppOps.checkOpNoThrow(OP_LEGACY_STORAGE, uid,
- packageName) == AppOpsManager.MODE_ALLOWED)) return true;
+ packageName) == AppOpsManager.MODE_ALLOWED)) {
+ return true;
+ }
if (enforce) {
throw new SecurityException("Op " + AppOpsManager.opToName(op) + " "
@@ -1924,7 +1976,7 @@ public class StorageManager {
return true;
}
if (mode == AppOpsManager.MODE_DEFAULT && mContext.checkPermission(
- MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) {
+ MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) {
return true;
}
// If app doesn't have MANAGE_EXTERNAL_STORAGE, then check if it has requested granular
@@ -1936,7 +1988,7 @@ public class StorageManager {
@VisibleForTesting
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
- throws IOException {
+ throws IOException {
Preconditions.checkNotNull(callback);
MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
// Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
@@ -1987,7 +2039,7 @@ public class StorageManager {
/** {@hide} */
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback)
- throws IOException {
+ throws IOException {
return openProxyFileDescriptor(mode, callback, null, null);
}
@@ -2006,19 +2058,18 @@ public class StorageManager {
* you're willing to decrypt on-demand, but where you want to avoid
* persisting the cleartext version.
*
- * @param mode The desired access mode, must be one of
- * {@link ParcelFileDescriptor#MODE_READ_ONLY},
- * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
- * {@link ParcelFileDescriptor#MODE_READ_WRITE}
+ * @param mode The desired access mode, must be one of
+ * {@link ParcelFileDescriptor#MODE_READ_ONLY},
+ * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+ * {@link ParcelFileDescriptor#MODE_READ_WRITE}
* @param callback Callback to process file operation requests issued on
- * returned file descriptor.
- * @param handler Handler that invokes callback methods.
+ * returned file descriptor.
+ * @param handler Handler that invokes callback methods.
* @return Seekable ParcelFileDescriptor.
- * @throws IOException
*/
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback, Handler handler)
- throws IOException {
+ throws IOException {
Preconditions.checkNotNull(handler);
return openProxyFileDescriptor(mode, callback, handler, null);
}
@@ -2050,10 +2101,10 @@ public class StorageManager {
* </p>
*
* @param storageUuid the UUID of the storage volume that you're interested
- * in. The UUID for a specific path can be obtained using
- * {@link #getUuidForPath(File)}.
+ * in. The UUID for a specific path can be obtained using
+ * {@link #getUuidForPath(File)}.
* @throws IOException when the storage device isn't present, or when it
- * doesn't support cache quotas.
+ * doesn't support cache quotas.
* @see #getCacheSizeBytes(UUID)
*/
@WorkerThread
@@ -2085,10 +2136,10 @@ public class StorageManager {
* </p>
*
* @param storageUuid the UUID of the storage volume that you're interested
- * in. The UUID for a specific path can be obtained using
- * {@link #getUuidForPath(File)}.
+ * in. The UUID for a specific path can be obtained using
+ * {@link #getUuidForPath(File)}.
* @throws IOException when the storage device isn't present, or when it
- * doesn't support cache quotas.
+ * doesn't support cache quotas.
* @see #getCacheQuotaBytes(UUID)
*/
@WorkerThread
@@ -2106,7 +2157,7 @@ public class StorageManager {
/** @hide */
- @IntDef(prefix = { "MOUNT_MODE_" }, value = {
+ @IntDef(prefix = {"MOUNT_MODE_"}, value = {
MOUNT_MODE_EXTERNAL_NONE,
MOUNT_MODE_EXTERNAL_DEFAULT,
MOUNT_MODE_EXTERNAL_INSTALLER,
@@ -2115,16 +2166,19 @@ public class StorageManager {
})
@Retention(RetentionPolicy.SOURCE)
/** @hide */
- public @interface MountMode {}
+ public @interface MountMode {
+ }
/**
* No external storage should be mounted.
+ *
* @hide
*/
@SystemApi
public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/**
* Default external storage should be mounted.
+ *
* @hide
*/
@SystemApi
@@ -2132,12 +2186,14 @@ public class StorageManager {
/**
* Mount mode for package installers which should give them access to
* all obb dirs in addition to their package sandboxes
+ *
* @hide
*/
@SystemApi
public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
/**
* The lower file system should be bind mounted directly on external storage
+ *
* @hide
*/
@SystemApi
@@ -2146,6 +2202,7 @@ public class StorageManager {
/**
* Use the regular scoped storage filesystem, but Android/ should be writable.
* Used to support the applications hosting DownloadManager and the MTP server.
+ *
* @hide
*/
@SystemApi
@@ -2164,10 +2221,10 @@ public class StorageManager {
* this flag to take effect.
* </p>
*
+ * @hide
* @see #getAllocatableBytes(UUID, int)
* @see #allocateBytes(UUID, long, int)
* @see #allocateBytes(FileDescriptor, long, int)
- * @hide
*/
@RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE)
@SystemApi
@@ -2194,6 +2251,7 @@ public class StorageManager {
* freeable cached space when determining allocatable space.
*
* Intended for use with {@link #getAllocatableBytes()}.
+ *
* @hide
*/
public static final int FLAG_ALLOCATE_NON_CACHE_ONLY = 1 << 3;
@@ -2203,12 +2261,13 @@ public class StorageManager {
* cached space when determining allocatable space.
*
* Intended for use with {@link #getAllocatableBytes()}.
+ *
* @hide
*/
public static final int FLAG_ALLOCATE_CACHE_ONLY = 1 << 4;
/** @hide */
- @IntDef(flag = true, prefix = { "FLAG_ALLOCATE_" }, value = {
+ @IntDef(flag = true, prefix = {"FLAG_ALLOCATE_"}, value = {
FLAG_ALLOCATE_AGGRESSIVE,
FLAG_ALLOCATE_DEFY_ALL_RESERVED,
FLAG_ALLOCATE_DEFY_HALF_RESERVED,
@@ -2216,7 +2275,8 @@ public class StorageManager {
FLAG_ALLOCATE_CACHE_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface AllocateFlags {}
+ public @interface AllocateFlags {
+ }
/**
* Return the maximum number of new bytes that your app can allocate for
@@ -2246,15 +2306,15 @@ public class StorageManager {
* </p>
*
* @param storageUuid the UUID of the storage volume where you're
- * considering allocating disk space, since allocatable space can
- * vary widely depending on the underlying storage device. The
- * UUID for a specific path can be obtained using
- * {@link #getUuidForPath(File)}.
+ * considering allocating disk space, since allocatable space can
+ * vary widely depending on the underlying storage device. The
+ * UUID for a specific path can be obtained using
+ * {@link #getUuidForPath(File)}.
* @return the maximum number of new bytes that the calling app can allocate
- * using {@link #allocateBytes(UUID, long)} or
- * {@link #allocateBytes(FileDescriptor, long)}.
+ * using {@link #allocateBytes(UUID, long)} or
+ * {@link #allocateBytes(FileDescriptor, long)}.
* @throws IOException when the storage device isn't present, or when it
- * doesn't support allocating space.
+ * doesn't support allocating space.
*/
@WorkerThread
public @BytesLong long getAllocatableBytes(@NonNull UUID storageUuid)
@@ -2297,12 +2357,12 @@ public class StorageManager {
* more than once every 60 seconds.
*
* @param storageUuid the UUID of the storage volume where you'd like to
- * allocate disk space. The UUID for a specific path can be
- * obtained using {@link #getUuidForPath(File)}.
- * @param bytes the number of bytes to allocate.
+ * allocate disk space. The UUID for a specific path can be
+ * obtained using {@link #getUuidForPath(File)}.
+ * @param bytes the number of bytes to allocate.
* @throws IOException when the storage device isn't present, or when it
- * doesn't support allocating space, or if the device had
- * trouble allocating the requested space.
+ * doesn't support allocating space, or if the device had
+ * trouble allocating the requested space.
* @see #getAllocatableBytes(UUID)
*/
@WorkerThread
@@ -2332,10 +2392,9 @@ public class StorageManager {
* These mount modes specify different views and access levels for
* different apps on external storage.
*
+ * @return {@code MountMode} for the given uid and packageName.
* @params uid UID of the application
* @params packageName name of the package
- * @return {@code MountMode} for the given uid and packageName.
- *
* @hide
*/
@RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
@@ -2366,15 +2425,15 @@ public class StorageManager {
* (such as when recording a video) you should avoid calling this method
* more than once every 60 seconds.
*
- * @param fd the open file that you'd like to allocate disk space for.
+ * @param fd the open file that you'd like to allocate disk space for.
* @param bytes the number of bytes to allocate. This is the desired final
- * size of the open file. If the open file is smaller than this
- * requested size, it will be extended without modifying any
- * existing contents. If the open file is larger than this
- * requested size, it will be truncated.
+ * size of the open file. If the open file is smaller than this
+ * requested size, it will be extended without modifying any
+ * existing contents. If the open file is larger than this
+ * requested size, it will be truncated.
* @throws IOException when the storage device isn't present, or when it
- * doesn't support allocating space, or if the device had
- * trouble allocating the requested space.
+ * doesn't support allocating space, or if the device had
+ * trouble allocating the requested space.
* @see #isAllocationSupported(FileDescriptor)
* @see Environment#isExternalStorageEmulated(File)
*/
@@ -2499,13 +2558,14 @@ public class StorageManager {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "QUOTA_TYPE_" }, value = {
+ @IntDef(prefix = {"QUOTA_TYPE_"}, value = {
QUOTA_TYPE_MEDIA_NONE,
QUOTA_TYPE_MEDIA_AUDIO,
QUOTA_TYPE_MEDIA_VIDEO,
QUOTA_TYPE_MEDIA_IMAGE,
})
- public @interface QuotaType {}
+ public @interface QuotaType {
+ }
private static native boolean setQuotaProjectId(String path, long projectId);
@@ -2532,15 +2592,13 @@ public class StorageManager {
* The default platform user of this API is the MediaProvider process, which is
* responsible for managing all of external storage.
*
- * @param path the path to the file for which we should update the quota type
+ * @param path the path to the file for which we should update the quota type
* @param quotaType the quota type of the file; this is based on the
* {@code QuotaType} constants, eg
* {@code StorageManager.QUOTA_TYPE_MEDIA_AUDIO}
- *
* @throws IllegalArgumentException if {@code quotaType} does not correspond to a valid
* quota type.
* @throws IOException if the quota type could not be updated.
- *
* @hide
*/
@SystemApi
@@ -2616,7 +2674,6 @@ public class StorageManager {
* permissions of a directory to what they should anyway be.
*
* @param path the path for which we should fix up the permissions
- *
* @hide
*/
public void fixupAppDir(@NonNull File path) {
@@ -2822,11 +2879,12 @@ public class StorageManager {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "APP_IO_BLOCKED_REASON_" }, value = {
- APP_IO_BLOCKED_REASON_TRANSCODING,
- APP_IO_BLOCKED_REASON_UNKNOWN,
+ @IntDef(prefix = {"APP_IO_BLOCKED_REASON_"}, value = {
+ APP_IO_BLOCKED_REASON_TRANSCODING,
+ APP_IO_BLOCKED_REASON_UNKNOWN,
})
- public @interface AppIoBlockedReason {}
+ public @interface AppIoBlockedReason {
+ }
/**
* Notify the system that an app with {@code uid} and {@code tid} is blocked on an IO request on
@@ -2839,10 +2897,9 @@ public class StorageManager {
* {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
*
* @param volumeUuid the UUID of the storage volume that the app IO is blocked on
- * @param uid the UID of the app blocked on IO
- * @param tid the tid of the app blocked on IO
- * @param reason the reason the app is blocked on IO
- *
+ * @param uid the UID of the app blocked on IO
+ * @param tid the tid of the app blocked on IO
+ * @param reason the reason the app is blocked on IO
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2866,10 +2923,9 @@ public class StorageManager {
* {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
*
* @param volumeUuid the UUID of the storage volume that the app IO is resumed on
- * @param uid the UID of the app resuming IO
- * @param tid the tid of the app resuming IO
- * @param reason the reason the app is resuming IO
- *
+ * @param uid the UID of the app resuming IO
+ * @param tid the tid of the app resuming IO
+ * @param reason the reason the app is resuming IO
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2890,10 +2946,9 @@ public class StorageManager {
* {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
*
* @param volumeUuid the UUID of the storage volume to check IO blocked status
- * @param uid the UID of the app to check IO blocked status
- * @param tid the tid of the app to check IO blocked status
- * @param reason the reason to check IO blocked status for
- *
+ * @param uid the UID of the app to check IO blocked status
+ * @param tid the tid of the app to check IO blocked status
+ * @param reason the reason to check IO blocked status for
* @hide
*/
@TestApi
@@ -2962,7 +3017,6 @@ public class StorageManager {
* information is available, -1 is returned.
*
* @return Percentage of the remaining useful lifetime of the internal storage device.
- *
* @hide
*/
@FlaggedApi(Flags.FLAG_STORAGE_LIFETIME_API)
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 195896dc8edf..0e78bfdb5069 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -180,7 +180,7 @@ public final class InputEventConsistencyVerifier {
final MotionEvent motionEvent = (MotionEvent)event;
if (motionEvent.isTouchEvent()) {
onTouchEvent(motionEvent, nestingLevel);
- } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ } else if (motionEvent.isFromSource(InputDevice.SOURCE_TRACKBALL)) {
onTrackballEvent(motionEvent, nestingLevel);
} else {
onGenericMotionEvent(motionEvent, nestingLevel);
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 1c36eaf99afa..9c1f134bff3e 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -290,9 +290,15 @@ public abstract class InputEventReceiver {
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchInputEvent(int seq, InputEvent event) {
- Trace.traceBegin(Trace.TRACE_TAG_INPUT, "dispatchInputEvent " + getShortDescription(event));
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_INPUT)) {
+ // This 'if' block is an optimization - without it, 'getShortDescription' will be
+ // called unconditionally, which is expensive.
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT,
+ "dispatchInputEvent " + getShortDescription(event));
+ }
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
+ // If tracing is not enabled, `traceEnd` is a no-op (so we don't need to guard it with 'if')
Trace.traceEnd(Trace.TRACE_TAG_INPUT);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 900f22d2b37b..0d6f82773622 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -133,6 +133,7 @@ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
+import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange;
import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -271,7 +272,9 @@ import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
+import android.window.WindowContext;
import android.window.WindowOnBackInvokedDispatcher;
+import android.window.WindowTokenClient;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -6609,12 +6612,26 @@ public final class ViewRootImpl implements ViewParent,
mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId,
activityWindowInfo);
} else {
- // There is no activity callback - update the configuration right away.
+ if (enableWindowContextResourcesUpdateOnConfigChange()) {
+ // There is no activity callback - update resources for window token, if needed.
+ final WindowTokenClient windowTokenClient = getWindowTokenClient();
+ if (windowTokenClient != null) {
+ windowTokenClient.onConfigurationChanged(
+ mLastReportedMergedConfiguration.getMergedConfiguration(),
+ newDisplayId == INVALID_DISPLAY ? mDisplay.getDisplayId()
+ : newDisplayId);
+ }
+ }
updateConfiguration(newDisplayId);
}
mForceNextConfigUpdate = false;
}
+ private WindowTokenClient getWindowTokenClient() {
+ if (!(mContext instanceof WindowContext)) return null;
+ return (WindowTokenClient) mContext.getWindowContextToken();
+ }
+
/**
* Update display and views if last applied merged configuration changed.
* @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS
index f62b33f1f753..799ef0091f71 100644
--- a/core/java/android/view/accessibility/OWNERS
+++ b/core/java/android/view/accessibility/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
# Android Accessibility Framework owners
include /services/accessibility/OWNERS
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0fb80422833c..56f0415b40cc 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3778,8 +3778,32 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
if (Flags.refactorInsetsController()) {
- mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
- false /* fromIme */, statsToken);
+ synchronized (mH) {
+ Handler vh = rootView.getHandler();
+ if (vh == null) {
+ // If the view doesn't have a handler, something has changed out from
+ // under us.
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE);
+
+ if (vh.getLooper() != Looper.myLooper()) {
+ // The view is running on a different thread than our own, so
+ // we need to reschedule our work for over there.
+ if (DEBUG) {
+ Log.v(TAG, "Close current input: reschedule hide to view thread");
+ }
+ final var viewRootImpl = mCurRootView;
+ vh.post(() -> viewRootImpl.getInsetsController().hide(
+ WindowInsets.Type.ime(), false /* fromIme */, statsToken));
+ } else {
+ mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
+ }
+ }
} else {
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 785246074cee..1ce5df7cd137 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -55,6 +55,7 @@ public enum DesktopModeFlags {
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
true),
+ ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 512113692c76..cf21e50e0a19 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1291,12 +1291,13 @@ public final class TransitionInfo implements Parcelable {
return options;
}
- /** Make options for a scale-up animation. */
+ /** Make options for a scale-up animation with task override option */
@NonNull
public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
- int height) {
+ int height, boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+ options.mOverrideTaskTransition = overrideTaskTransition;
return options;
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a551fe701c5b..f7bee619bc4b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -106,7 +106,6 @@ public class WindowTokenClient extends Binder {
* @param newConfig the updated {@link Configuration}
* @param newDisplayId the updated {@link android.view.Display} ID
*/
- @VisibleForTesting(visibility = PACKAGE)
@MainThread
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 09c6dc0e2b20..b805ac560b8d 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -677,6 +677,17 @@ flag {
}
flag {
+ name: "enable_window_context_resources_update_on_config_change"
+ namespace: "lse_desktop_experience"
+ description: "Updates window context resources before the view receives the config change callback."
+ bug: "394527409"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_tab_tearing_minimize_animation_bugfix"
namespace: "lse_desktop_experience"
description: "Enabling a minimize animation when a new window is opened via tab tearing and the Desktop Windowing open windows limit is reached."
@@ -684,4 +695,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "enable_desktop_close_shortcut_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Fix the window-close keyboard shortcut in Desktop Mode."
+ bug: "394599430"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/OWNERS b/core/java/com/android/internal/accessibility/OWNERS
index 1265dfa2c441..dac64f47ba7e 100644
--- a/core/java/com/android/internal/accessibility/OWNERS
+++ b/core/java/com/android/internal/accessibility/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
# Android Accessibility Framework owners
include /services/accessibility/OWNERS \ No newline at end of file
diff --git a/core/java/com/android/internal/graphics/palette/OWNERS b/core/java/com/android/internal/graphics/palette/OWNERS
index 731dca9b128f..df867252c01c 100644
--- a/core/java/com/android/internal/graphics/palette/OWNERS
+++ b/core/java/com/android/internal/graphics/palette/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 484670
-dupin@google.com
-jamesoleary@google.com \ No newline at end of file
+# Bug component: 484670
+dupin@google.com
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 05a33fe830e8..d8cf258e23ba 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -160,19 +160,21 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
Objects.requireNonNull(mConfigurationService,
"A null ProtoLog Configuration Service was provided!");
- try {
- var args = createConfigurationServiceRegisterClientArgs();
+ mBackgroundLoggingService.execute(() -> {
+ try {
+ var args = createConfigurationServiceRegisterClientArgs();
- final var groupArgs = mLogGroups.values().stream()
- .map(group -> new RegisterClientArgs
- .GroupConfig(group.name(), group.isLogToLogcat()))
- .toArray(RegisterClientArgs.GroupConfig[]::new);
- args.setGroups(groupArgs);
+ final var groupArgs = mLogGroups.values().stream()
+ .map(group -> new RegisterClientArgs
+ .GroupConfig(group.name(), group.isLogToLogcat()))
+ .toArray(RegisterClientArgs.GroupConfig[]::new);
+ args.setGroups(groupArgs);
- mConfigurationService.registerClient(this, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to register ProtoLog client");
- }
+ mConfigurationService.registerClient(this, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to register ProtoLog client");
+ }
+ });
}
/**
diff --git a/core/java/com/android/internal/protolog/WmProtoLogGroups.java b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
index 4bd5d24b71e2..5edc2fbd4c8f 100644
--- a/core/java/com/android/internal/protolog/WmProtoLogGroups.java
+++ b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
@@ -100,6 +100,8 @@ public enum WmProtoLogGroups implements IProtoLogGroup {
WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_EMBEDDED_WINDOWS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_PRESENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 4ff3f8825cc4..ef5875eff06f 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -110,4 +110,8 @@
tap power gesture from triggering the selected target action.
-->
<integer name="config_doubleTapPowerGestureMode">0</integer>
+
+ <!-- By default ActivityOptions#makeScaleUpAnimation is only used between activities. This
+ config enables OEMs to support its usage across tasks.-->
+ <bool name="config_enableCrossTaskScaleUpAnimation">true</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b3581d98face..17acf9aed278 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7256,6 +7256,9 @@
<!-- Wear devices: An intent action that is used for remote intent. -->
<string name="config_wearRemoteIntentAction" translatable="false" />
+ <!-- Whether the current device's internal display can host desktop sessions. -->
+ <bool name="config_canInternalDisplayHostDesktops">false</bool>
+
<!-- Whether desktop mode is supported on the current device -->
<bool name="config_isDesktopModeSupported">false</bool>
@@ -7348,4 +7351,23 @@
<!-- Array containing the notification assistant service adjustments that are not supported by
default on this device-->
<string-array translatable="false" name="config_notificationDefaultUnsupportedAdjustments" />
+
+ <!-- Preference name of bugreport-->
+ <string name="prefs_bugreport" translatable="false">bugreports</string>
+
+ <!-- key value of warning state stored in bugreport preference-->
+ <string name="key_warning_state" translatable="false">warning-state</string>
+
+ <!-- Bugreport warning dialog state unknown-->
+ <integer name="bugreport_state_unknown">0</integer>
+
+ <!-- Bugreport warning dialog state shows the warning dialog-->
+ <integer name="bugreport_state_show">1</integer>
+
+ <!-- Bugreport warning dialog state skips the warning dialog-->
+ <integer name="bugreport_state_hide">2</integer>
+
+ <!-- By default ActivityOptions#makeScaleUpAnimation is only used between activities. This
+ config enables OEMs to support its usage across tasks.-->
+ <bool name="config_enableCrossTaskScaleUpAnimation">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9393aa4b6086..cc2897a2779e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5754,6 +5754,9 @@
<!-- Whether the developer option for desktop mode is supported on the current device -->
<java-symbol type="bool" name="config_isDesktopModeDevOptionSupported" />
+ <!-- Whether the current device's internal display can host desktop sessions. -->
+ <java-symbol type="bool" name="config_canInternalDisplayHostDesktops" />
+
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
@@ -5902,4 +5905,14 @@
<java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_text" />
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_title" />
<java-symbol type="string" name="usb_apm_usb_suspicious_activity_notification_text" />
+
+ <java-symbol type="string" name="prefs_bugreport" />
+ <java-symbol type="string" name="key_warning_state" />
+ <java-symbol type="integer" name="bugreport_state_unknown" />
+ <java-symbol type="integer" name="bugreport_state_show" />
+ <java-symbol type="integer" name="bugreport_state_hide" />
+
+ <!-- Enable OEMs to support scale up anim across tasks.-->
+ <java-symbol type="bool" name="config_enableCrossTaskScaleUpAnimation" />
+
</resources>
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa510381060..dc2f0a69375d 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@ public class DisplayManagerGlobalTest {
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+ assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+ mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+ 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index dfded7321b2c..0c4ea79dd5be 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -102,6 +102,10 @@ public final class Bitmap implements Parcelable {
private static volatile int sDefaultDensity = -1;
+ /**
+ * This id is not authoritative and can be duplicated if an ashmem bitmap is decoded from a
+ * parcel.
+ */
private long mId;
/**
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 13d0169c47c5..a08f88a5b937 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -177,3 +177,10 @@ flag {
description: "Factor task-view state tracking out of taskviewtransitions"
bug: "384976265"
}
+
+flag {
+ name: "enable_bubble_bar_on_phones"
+ namespace: "multitasking"
+ description: "Try out bubble bar on phones"
+ bug: "394869612"
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 2586bd6d86cb..643c1506e4c2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -220,6 +220,13 @@ public class DesktopModeStatus {
}
/**
+ * Return {@code true} if the current device can host desktop sessions on its internal display.
+ */
+ public static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
+ }
+
+ /**
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopModeDevOption(@NonNull Context context) {
@@ -231,21 +238,24 @@ public class DesktopModeStatus {
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
- return Flags.showDesktopExperienceDevOption() && isDeviceEligibleForDesktopMode(context);
+ return Flags.showDesktopExperienceDevOption()
+ && isInternalDisplayEligibleToHostDesktops(context);
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
public static boolean shouldDevOptionBeEnabledByDefault(Context context) {
- return isDeviceEligibleForDesktopMode(context) && Flags.enableDesktopWindowingMode();
+ return isInternalDisplayEligibleToHostDesktops(context)
+ && Flags.enableDesktopWindowingMode();
}
/**
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
*/
public static boolean canEnterDesktopMode(@NonNull Context context) {
- return (isDeviceEligibleForDesktopMode(context)
- && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
- || isDesktopModeEnabledByDevOption(context);
+ return (isInternalDisplayEligibleToHostDesktops(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()
+ && (isDesktopModeSupported(context) || !enforceDeviceRestrictions())
+ || isDesktopModeEnabledByDevOption(context));
}
/**
@@ -313,10 +323,11 @@ public class DesktopModeStatus {
}
/**
- * Return {@code true} if desktop mode is unrestricted and is supported in the device.
+ * Return {@code true} if desktop sessions is unrestricted and can be host for the device's
+ * internal display.
*/
- public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
- return !enforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ public static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) {
+ return !enforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || (
Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(
context));
}
@@ -325,7 +336,7 @@ public class DesktopModeStatus {
* Return {@code true} if the developer option for desktop mode is unrestricted and is supported
* in the device.
*
- * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
+ * Note that, if {@link #isInternalDisplayEligibleToHostDesktops(Context)} is true, then
* {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
*/
private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 8cf2370df48d..c7a0401c2b88 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
@@ -793,15 +793,21 @@ public class BubbleController implements ConfigurationChangeListener,
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
@BubbleBarLocation.UpdateSource int source) {
if (isShowingAsBubbleBar()) {
+ updateExpandedViewForBubbleBarLocation(bubbleBarLocation, source);
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
+ mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+ }
+ }
+
+ private void updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+ @BubbleBarLocation.UpdateSource int source) {
+ if (isShowingAsBubbleBar()) {
BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
mLayerView.updateExpandedView();
}
- BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
- bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
- mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
-
logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
}
}
@@ -874,7 +880,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void onItemDroppedOverBubbleBarDragZone(BubbleBarLocation location, Intent itemIntent) {
+ public void onItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
+ Intent itemIntent) {
hideBubbleBarExpandedViewDropTarget();
ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent
.getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO);
@@ -1521,18 +1528,19 @@ public class BubbleController implements ConfigurationChangeListener,
public void expandStackAndSelectBubble(ShortcutInfo info,
@Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
- if (bubbleBarLocation != null) {
- //TODO (b/388894910) combine location update with the setSelectedBubbleAndExpandStack &
- // fix bubble bar flicking
- setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
+ BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null;
+ if (updateLocation != null) {
+ updateExpandedViewForBubbleBarLocation(updateLocation,
+ BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
}
Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
if (b.isInflated()) {
- mBubbleData.setSelectedBubbleAndExpandStack(b);
+ mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation);
} else {
b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false,
+ updateLocation);
}
}
@@ -1562,19 +1570,19 @@ public class BubbleController implements ConfigurationChangeListener,
public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user,
@Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
- if (bubbleBarLocation != null) {
- //TODO (b/388894910) combine location update with the setSelectedBubbleAndExpandStack &
- // fix bubble bar flicking
- setBubbleBarLocation(bubbleBarLocation, BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
+ BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null;
+ if (updateLocation != null) {
+ updateExpandedViewForBubbleBarLocation(updateLocation,
+ BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
}
Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user);
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s",
pendingIntent);
if (b.isInflated()) {
- mBubbleData.setSelectedBubbleAndExpandStack(b);
+ mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation);
} else {
b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
+ inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false, updateLocation);
}
}
@@ -1940,11 +1948,22 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
+ inflateAndAdd(bubble, suppressFlyout, showInShade, /* bubbleBarLocation= */ null);
+ }
+
+ /**
+ * Inflates and adds a bubble. Updates Bubble Bar location if bubbles
+ * are shown in the Bubble Bar and the location is not null.
+ */
+ @VisibleForTesting
+ public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade,
+ @Nullable BubbleBarLocation bubbleBarLocation) {
// Lazy init stack view when a bubble is created
ensureBubbleViewsAndWindowCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.inflate(
- b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade,
+ bubbleBarLocation),
mContext,
mExpandedViewManager,
mBubbleTaskViewFactory,
@@ -2278,7 +2297,8 @@ public class BubbleController implements ConfigurationChangeListener,
ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ " expanded=%b selectionChanged=%b selected=%s"
- + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b",
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b"
+ + " bubbleBarLocation=%s",
update.addedBubble != null ? update.addedBubble.getKey() : "null",
!update.removedBubbles.isEmpty(),
update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
@@ -2287,7 +2307,9 @@ public class BubbleController implements ConfigurationChangeListener,
update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
- update.shouldShowEducation, update.showOverflowChanged);
+ update.shouldShowEducation, update.showOverflowChanged,
+ update.mBubbleBarLocation != null ? update.mBubbleBarLocation.toString()
+ : "null");
ensureBubbleViewsAndWindowCreated();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index f97133a4c3d1..abcdb7e70cec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -43,6 +43,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.bubbles.RemovedBubble;
@@ -91,6 +92,8 @@ public class BubbleData {
@Nullable Bubble suppressedBubble;
@Nullable Bubble unsuppressedBubble;
@Nullable String suppressedSummaryGroup;
+ @Nullable
+ BubbleBarLocation mBubbleBarLocation;
// Pair with Bubble and @DismissReason Integer
final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();
@@ -116,6 +119,7 @@ public class BubbleData {
|| unsuppressedBubble != null
|| suppressedSummaryChanged
|| suppressedSummaryGroup != null
+ || mBubbleBarLocation != null
|| showOverflowChanged;
}
@@ -169,6 +173,7 @@ public class BubbleData {
}
bubbleBarUpdate.showOverflowChanged = showOverflowChanged;
bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
+ bubbleBarUpdate.bubbleBarLocation = mBubbleBarLocation;
return bubbleBarUpdate;
}
@@ -396,8 +401,23 @@ public class BubbleData {
* {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates.
*/
public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) {
+ setSelectedBubbleAndExpandStack(bubble, /* bubbleBarLocation = */ null);
+ }
+
+ /**
+ * Sets the selected bubble and expands it. Also updates bubble bar location if the
+ * bubbleBarLocation is not {@code null}
+ *
+ * <p>This dispatches a single state update for 3 changes and should be used instead of
+ * calling {@link BubbleController#setBubbleBarLocation(BubbleBarLocation, int)} followed by
+ * {@link #setSelectedBubbleAndExpandStack(BubbleViewProvider)} immediately after, which will
+ * generate 2 separate updates.
+ */
+ public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble,
+ @Nullable BubbleBarLocation bubbleBarLocation) {
setSelectedBubbleInternal(bubble);
setExpandedInternal(true);
+ mStateChange.mBubbleBarLocation = bubbleBarLocation;
dispatchPendingChanges();
}
@@ -513,13 +533,25 @@ public class BubbleData {
}
/**
+ * Calls {@link #notificationEntryUpdated(Bubble, boolean, boolean, BubbleBarLocation)} passing
+ * {@code null} for bubbleBarLocation.
+ *
+ * @see #notificationEntryUpdated(Bubble, boolean, boolean, BubbleBarLocation)
+ */
+ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
+ notificationEntryUpdated(bubble, suppressFlyout, showInShade, /* bubbleBarLocation = */
+ null);
+ }
+
+ /**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
* BubbleTaskViewFactory, BubblePositioner, BubbleLogger, BubbleStackView,
* com.android.wm.shell.bubbles.bar.BubbleBarLayerView,
* com.android.launcher3.icons.BubbleIconFactory, boolean)
*/
- void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
+ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade,
+ @Nullable BubbleBarLocation bubbleBarLocation) {
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.isTextChanged();
@@ -567,6 +599,7 @@ public class BubbleData {
doSuppress(bubble);
}
}
+ mStateChange.mBubbleBarLocation = bubbleBarLocation;
dispatchPendingChanges();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index aa42de67152a..e3b0872df593 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -524,8 +524,8 @@ public class BubbleBarLayerView extends FrameLayout
* Skips logging if it is {@link BubbleOverflow}.
*/
private void logBubbleEvent(BubbleLogger.Event event) {
- if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) {
- mBubbleLogger.log(bubble, event);
+ if (mExpandedBubble != null && mExpandedBubble instanceof Bubble) {
+ mBubbleLogger.log((Bubble) mExpandedBubble, event);
}
}
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 b2b99d648bf4..b6012378e4d4 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
@@ -914,12 +914,15 @@ public abstract class WMShellModule {
Context context,
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
InteractionJankMonitor interactionJankMonitor) {
return ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()
? new SpringDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
- context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+ context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
+ interactionJankMonitor);
}
@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 7491abd4248b..531304d6922a 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
@@ -1659,11 +1659,16 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+ // If the wallpaper activity for this display already exists, let's reorder it to top.
+ val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+ if (wallpaperActivityToken != null) {
+ wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+ return
+ }
+
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (
- desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
- Flags.enablePerDisplayDesktopWallpaperActivity()
- ) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
index a5ba6612bb1a..c10752d36bf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -90,6 +90,11 @@ class DesktopUserRepositories(
return desktopRepoByUserId.getOrCreate(profileId)
}
+ fun getUserIdForProfile(profileId: Int): Int {
+ if (userIdToProfileIdsMap[userId]?.contains(profileId) == true) return userId
+ else return profileId
+ }
+
/** Dumps [DesktopRepository] for each user. */
fun dump(pw: PrintWriter, prefix: String) {
desktopRepoByUserId.forEach { key, value ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 2ac76f319d32..8194d3cab445 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -70,6 +70,7 @@ sealed class DragToDesktopTransitionHandler(
private val context: Context,
private val transitions: Transitions,
private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val desktopUserRepositories: DesktopUserRepositories,
protected val interactionJankMonitor: InteractionJankMonitor,
protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {
@@ -127,15 +128,18 @@ sealed class DragToDesktopTransitionHandler(
pendingIntentCreatorBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
- val taskUser = UserHandle.of(taskInfo.userId)
+ // If we are launching home for a profile of a user, just use the [userId] of that user
+ // instead of the [profileId] to create the context.
+ val userToLaunchWith =
+ UserHandle.of(desktopUserRepositories.getUserIdForProfile(taskInfo.userId))
val pendingIntent =
PendingIntent.getActivityAsUser(
- context.createContextAsUser(taskUser, /* flags= */ 0),
+ context.createContextAsUser(userToLaunchWith, /* flags= */ 0),
/* requestCode= */ 0,
launchHomeIntent,
FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
options.toBundle(),
- taskUser,
+ userToLaunchWith,
)
val wct = WindowContainerTransaction()
// The app that is being dragged into desktop mode might cause new transitions, make this
@@ -881,6 +885,7 @@ constructor(
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -890,6 +895,7 @@ constructor(
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
@@ -917,6 +923,7 @@ constructor(
context: Context,
transitions: Transitions,
taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ desktopUserRepositories: DesktopUserRepositories,
interactionJankMonitor: InteractionJankMonitor,
transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
SurfaceControl.Transaction()
@@ -926,6 +933,7 @@ constructor(
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
interactionJankMonitor,
transactionSupplier,
) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index ae8f8c4eff79..7751741ae082 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -46,6 +47,7 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
@@ -73,6 +75,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.Flags;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
@@ -1353,6 +1356,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
t.show(mPausingTasks.get(i).mTaskSurface);
}
+ setCornerRadiusForFreeformTasks(
+ mRecentTasksController.getContext(), t, mPausingTasks);
if (!mKeyguardLocked && mRecentsTask != null) {
wct.restoreTransientOrder(mRecentsTask);
}
@@ -1390,6 +1395,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
for (int i = 0; i < mOpeningTasks.size(); ++i) {
t.show(mOpeningTasks.get(i).mTaskSurface);
}
+ setCornerRadiusForFreeformTasks(
+ mRecentTasksController.getContext(), t, mOpeningTasks);
for (int i = 0; i < mPausingTasks.size(); ++i) {
cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint);
}
@@ -1450,6 +1457,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
wct.clear();
if (Flags.enableRecentsBookendTransition()) {
+ // Notify the mixers of the pending finish
+ for (int i = 0; i < mMixers.size(); ++i) {
+ mMixers.get(i).handleFinishRecents(returningToApp, wct, t);
+ }
+
// In this case, we've already started the PIP transition, so we can
// clean up immediately
mPendingRunnerFinishCb = runnerFinishCb;
@@ -1509,6 +1521,27 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
}
+ private static void setCornerRadiusForFreeformTasks(
+ Context context,
+ SurfaceControl.Transaction t,
+ ArrayList<TaskState> tasks) {
+ if (!ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) {
+ return;
+ }
+ int cornerRadius = getCornerRadius(context);
+ for (int i = 0; i < tasks.size(); ++i) {
+ TaskState task = tasks.get(i);
+ if (task.mTaskInfo != null && task.mTaskInfo.isFreeform()) {
+ t.setCornerRadius(task.mTaskSurface, cornerRadius);
+ }
+ }
+ }
+
+ private static int getCornerRadius(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius);
+ }
+
private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
if (tasks == null) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index aff21cbe0ae6..15ac03ccaf30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1675,8 +1675,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct, @ExitReason int exitReason) {
if (!isSplitActive()) return;
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%s",
- stageTypeToString(stageToTop));
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%s reason=%s",
+ stageTypeToString(stageToTop), exitReasonToString(exitReason));
if (enableFlexibleSplit()) {
mStageOrderOperator.getActiveStages().stream()
.filter(stage -> stage.getId() != stageToTop)
@@ -3395,12 +3395,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
TransitionInfo.Change sideChild = null;
StageTaskListener firstAppStage = null;
StageTaskListener secondAppStage = null;
+ boolean foundPausingTask = false;
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null || !taskInfo.hasParentTask()) continue;
if (mPausingTasks.contains(taskInfo.taskId)) {
+ foundPausingTask = true;
continue;
}
StageTaskListener stage = getStageOfTask(taskInfo);
@@ -3443,9 +3445,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareExitSplitScreen(dismissTop, cancelWct, EXIT_REASON_UNKNOWN);
logExit(EXIT_REASON_UNKNOWN);
});
- Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
- "launched 2 tasks in split, but didn't receive "
- + "2 tasks in transition. Possibly one of them failed to launch"));
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "launched 2 tasks in "
+ + "split, but didn't receive 2 tasks in transition. Possibly one of them "
+ + "failed to launch (foundPausingTask=" + foundPausingTask + ")"));
if (mRecentTasks.isPresent() && mainChild != null) {
mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId);
}
@@ -3800,6 +3802,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation canceled during split-screen. */
public void onRecentsInSplitAnimationCanceled() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationCanceled");
mPausingTasks.clear();
setSplitsVisible(false);
@@ -3809,31 +3812,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
- public void onRecentsInSplitAnimationFinishing(boolean returnToApp,
- @NonNull WindowContainerTransaction finishWct,
- @NonNull SurfaceControl.Transaction finishT) {
- if (!Flags.enableRecentsBookendTransition()) {
- // The non-bookend recents transition case will be handled by
- // RecentsMixedTransition wrapping the finish callback and calling
- // onRecentsInSplitAnimationFinish()
- return;
- }
-
- onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT);
- }
-
- /** Call this when the recents animation during split-screen finishes. */
- public void onRecentsInSplitAnimationFinish(@NonNull WindowContainerTransaction finishWct,
- @NonNull SurfaceControl.Transaction finishT) {
- if (Flags.enableRecentsBookendTransition()) {
- // The bookend recents transition case will be handled by
- // onRecentsInSplitAnimationFinishing above
- return;
- }
-
- // Check if the recent transition is finished by returning to the current
- // split, so we can restore the divider bar.
- boolean returnToApp = false;
+ /**
+ * Returns whether the given WCT is reordering any of the split tasks to top.
+ */
+ public boolean wctIsReorderingSplitToTop(@NonNull WindowContainerTransaction finishWct) {
for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
final WindowContainerTransaction.HierarchyOp op =
finishWct.getHierarchyOps().get(i);
@@ -3848,14 +3830,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
&& anyStageContainsContainer) {
- returnToApp = true;
+ return true;
}
}
- onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT);
+ return false;
}
- /** Call this when the recents animation during split-screen finishes. */
- public void onRecentsInSplitAnimationFinishInner(boolean returnToApp,
+ /** Called when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinishing(boolean returnToApp,
@NonNull WindowContainerTransaction finishWct,
@NonNull SurfaceControl.Transaction finishT) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish: returnToApp=%b",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index f40dc8ad93b5..1e926c57ca61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -159,9 +159,17 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
// If pair-to-pair switching, the post-recents clean-up isn't needed.
wct = wct != null ? wct : new WindowContainerTransaction();
if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
- // TODO(b/346588978): Only called if !enableRecentsBookendTransition(), can remove
- // once that rolls out
- mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ // We've dispatched to the mLeftoversHandler to handle the rest of the transition
+ // and called onRecentsInSplitAnimationStart(), but if the recents handler is not
+ // actually handling the transition, then onRecentsInSplitAnimationFinishing()
+ // won't actually get called by the recents handler. In such cases, we still need
+ // to clean up after the changes from the start call.
+ boolean splitNotifiedByRecents = mRecentsHandler == mLeftoversHandler;
+ if (!splitNotifiedByRecents) {
+ mSplitHandler.onRecentsInSplitAnimationFinishing(
+ mSplitHandler.wctIsReorderingSplitToTop(wct),
+ wct, finishTransaction);
+ }
} else {
// notify pair-to-pair recents animation finish
mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 19829e7e5677..bac8e5062128 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -12,7 +12,6 @@ atsjenk@google.com
jorgegil@google.com
vaniadesmonda@google.com
pbdr@google.com
-tkachenkoi@google.com
mpodolian@google.com
jeremysim@google.com
peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 02b2cec8dbdb..ae73dae99d6f 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -53,10 +53,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index ffcc3446d436..7a7d88b80ce3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -572,6 +572,22 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.shouldShowEducation).isTrue();
}
+ /** Verifies that the update should contain the bubble bar location. */
+ @Test
+ public void test_shouldUpdateBubbleBarLocation() {
+ // Setup
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true, BubbleBarLocation.LEFT);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.mBubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+ }
+
/**
* Verifies that the update shouldn't show the user education, if the education is required but
* the bubble should auto-expand
@@ -1367,6 +1383,20 @@ public class BubbleDataTest extends ShellTestCase {
}
@Test
+ public void setSelectedBubbleAndExpandStackWithLocation() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1, BubbleBarLocation.LEFT);
+
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA1);
+ assertExpandedChangedTo(true);
+ assertLocationChangedTo(BubbleBarLocation.LEFT);
+ }
+
+ @Test
public void testShowOverflowChanged_hasOverflowBubbles() {
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
sendUpdatedEntryAtTime(mEntryA1, 1000);
@@ -1450,6 +1480,12 @@ public class BubbleDataTest extends ShellTestCase {
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
+ private void assertLocationChangedTo(BubbleBarLocation location) {
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertWithMessage("locationChanged").that(update.mBubbleBarLocation)
+ .isEqualTo(location);
+ }
+
private void assertExpandedChangedTo(boolean expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index d6b13610c9c1..70a30a3ca7a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -113,7 +113,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 403d468a7034..d510570e8839 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -30,7 +30,6 @@ import android.view.KeyEvent
import android.window.DisplayAreaInfo
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
@@ -48,7 +47,6 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
@@ -107,12 +105,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
@Before
fun setUp() {
Dispatchers.setMain(StandardTestDispatcher())
- mockitoSession =
- mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
+ mockitoSession = mockitoSession().strictness(Strictness.LENIENT).startMocking()
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
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 cd1c16a93475..718bf322f6a9 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
@@ -171,7 +171,6 @@ import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
@@ -292,7 +291,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(Toast::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
@@ -363,9 +362,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
shellInit.init()
- val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ val captor = argumentCaptor<RecentsTransitionStateListener>()
verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
- recentsTransitionStateListener = captor.value
+ recentsTransitionStateListener = captor.firstValue
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
@@ -441,7 +440,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
val task1 = setUpFreeformTask()
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -461,7 +460,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
STABLE_BOUNDS.height(),
displayController,
)
- assertThat(argumentCaptor.value).isTrue()
+ assertThat(argumentCaptor.firstValue).isTrue()
}
@Test
@@ -476,7 +475,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ val argumentCaptor = argumentCaptor<Boolean>()
controller.toggleDesktopTaskSize(
task1,
ToggleTaskSizeInteraction(
@@ -497,7 +496,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(displayController),
anyOrNull(),
)
- assertThat(argumentCaptor.value).isFalse()
+ assertThat(argumentCaptor.firstValue).isFalse()
}
@Test
@@ -547,6 +546,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -581,7 +581,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val wct =
getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
// Wallpaper is moved to front.
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
// Desk is activated.
verify(desksOrganizer).activateDesk(wct, deskId)
}
@@ -783,6 +783,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -825,7 +826,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -842,6 +844,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
wct.assertReorderAt(index = 2, task2)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
@Test
@DisableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -860,9 +880,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
- whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
- .thenReturn(Binder())
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -871,10 +891,18 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
- )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -899,6 +927,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
.thenReturn(Binder())
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -991,6 +1020,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
/** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1569,6 +1599,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1736,7 +1767,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1751,12 +1782,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
@@ -1768,7 +1799,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desktopModeEnterExitTransitionListener)
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
@@ -1802,6 +1833,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1828,6 +1860,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() {
val freeformTask = setUpFreeformTask()
@@ -1840,7 +1873,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
)
val wct = getLatestEnterDesktopWct()
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 0, wallpaperToken)
verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
verify(desksOrganizer).activateDesk(wct, deskId = 0)
verify(desktopModeEnterExitTransitionListener)
@@ -1967,6 +2000,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2224,26 +2258,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.firstValue)
}
@Test
fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
setUpHomeTask()
val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
- assertThat(transitionHandlerArgCaptor.value)
+ assertThat(transitionHandlerArgCaptor.firstValue)
.isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
}
@@ -2718,9 +2752,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2759,9 +2793,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2775,9 +2809,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+ captor.firstValue.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
}
@Test
@@ -2791,10 +2825,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// The only active task is being minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2808,9 +2842,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// The only active task is already minimized.
controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2825,9 +2859,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
+ captor.firstValue.hierarchyOps.none { hop ->
hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
}
}
@@ -2845,10 +2879,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
// task1 is the only visible task as task2 is minimized.
controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
// Adds remove wallpaper operation
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val captor = argumentCaptor<WindowContainerTransaction>()
verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- captor.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -2987,6 +3021,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -3118,6 +3153,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -3152,7 +3188,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
+
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -3180,6 +3218,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -4635,7 +4674,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4643,9 +4682,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
eq(task2.configuration.windowConfiguration.bounds),
)
- assertThat(wctArgument.value.hierarchyOps).hasSize(1)
+ assertThat(wctArgument.firstValue.hierarchyOps).hasSize(1)
// Removes wallpaper activity when leaving desktop
- wctArgument.value.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ wctArgument.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
}
@Test
@@ -4660,7 +4699,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val wctArgument = argumentCaptor<WindowContainerTransaction>()
verify(splitScreenController)
.requestEnterSplitSelect(
eq(task2),
@@ -4669,7 +4708,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(task2.configuration.windowConfiguration.bounds),
)
// Does not remove wallpaper activity, as desktop still has visible desktop tasks
- assertThat(wctArgument.value.hierarchyOps).isEmpty()
+ assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
}
@Test
@@ -4677,7 +4716,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun newWindow_fromFullscreenOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4690,7 +4729,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4699,7 +4738,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun newWindow_fromSplitOpensInSplit() {
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(
@@ -4712,7 +4751,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
eq(true),
eq(SPLIT_INDEX_UNDEFINED),
)
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4807,11 +4846,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
setUpLandscapeDisplay()
val task = setUpFullscreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -4821,11 +4860,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
setUpLandscapeDisplay()
val task = setUpSplitScreenTask()
val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ val optionsCaptor = argumentCaptor<Bundle>()
runOpenInstance(task, taskToRequest.taskId)
verify(splitScreenController)
.startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -5912,35 +5951,37 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
mockDragEvent,
mockCallback as Consumer<Boolean>,
)
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
var expectedWindowingMode: Int
if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
// Fullscreen launches currently use default transitions
- verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
} else {
expectedWindowingMode = WINDOWING_MODE_FREEFORM
if (tabTearingAnimationFlagEnabled) {
verify(desktopMixedTransitionHandler)
.startLaunchTransition(
eq(TRANSIT_OPEN),
- capture(arg),
+ arg.capture(),
anyOrNull(),
anyOrNull(),
anyOrNull(),
)
} else {
// All other launches use a special handler.
- verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ verify(dragAndDropTransitionHandler).handleDropEvent(arg.capture())
}
}
assertThat(
- ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
.launchWindowingMode
)
.isEqualTo(expectedWindowingMode)
- assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds)
+ assertThat(
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+ .launchBounds
+ )
.isEqualTo(expectedBounds)
}
@@ -6122,52 +6163,49 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
handlerClass: Class<out TransitionHandler>? = null,
): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
if (handlerClass == null) {
verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
}
- return arg.value
+ return arg.lastValue
}
private fun getLatestToggleResizeDesktopTaskWct(
currentBounds: Rect? = null
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
- return arg.value
+ .startTransition(arg.capture(), eq(currentBounds))
+ return arg.lastValue
}
private fun getLatestDesktopMixedTaskWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN
): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(desktopMixedTransitionHandler)
- .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
- return arg.value
+ .startLaunchTransition(eq(type), arg.capture(), anyOrNull(), anyOrNull(), anyOrNull())
+ return arg.lastValue
}
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- return arg.value
+ return arg.lastValue
}
private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- return arg.value
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(arg.capture())
+ return arg.lastValue
}
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ val arg = argumentCaptor<WindowContainerTransaction>()
verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- return arg.value
+ return arg.lastValue
}
private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index 83e48728c4f2..030bb1ace49d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -123,8 +123,26 @@ class DesktopUserRepositoriesTest : ShellTestCase() {
assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun getUserForProfile_flagEnabled_returnsUserIdForProfile() {
+ userRepositories.onUserChanged(USER_ID_2, mock())
+ val profiles: MutableList<UserInfo> =
+ mutableListOf(
+ UserInfo(USER_ID_2, "User profile", 0),
+ UserInfo(PROFILE_ID_1, "Work profile", 0),
+ )
+ userRepositories.onUserProfilesChanged(profiles)
+
+ val userIdForProfile = userRepositories.getUserIdForProfile(PROFILE_ID_1)
+
+ assertThat(userIdForProfile).isEqualTo(USER_ID_2)
+ }
+
private companion object {
const val USER_ID_1 = 7
+ const val USER_ID_2 = 8
+ const val PROFILE_ID_1 = 4
const val PROFILE_ID_2 = 5
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 25246d9984c3..1732875f1d57 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -70,6 +70,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var draggedTaskLeash: SurfaceControl
@Mock private lateinit var homeTaskLeash: SurfaceControl
+ @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -84,6 +85,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -93,6 +95,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
context,
transitions,
taskDisplayAreaOrganizer,
+ desktopUserRepositories,
mockInteractionJankMonitor,
transactionSupplier,
)
@@ -484,17 +487,22 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
- val startTransition = startDrag(
- springHandler, task, finishTransaction = playingFinishTransaction, homeChange = null)
+ val startTransition =
+ startDrag(
+ springHandler,
+ task,
+ finishTransaction = playingFinishTransaction,
+ homeChange = null,
+ )
springHandler.onTaskResizeAnimationListener = mock()
springHandler.mergeAnimation(
transition = mock<IBinder>(),
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task,
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
startT = mergedStartTransaction,
finishT = mergedFinishTransaction,
mergeTarget = startTransition,
@@ -723,7 +731,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun createTransitionInfo(
type: Int,
draggedTask: RunningTaskInfo,
- homeChange: TransitionInfo.Change? = createHomeChange()) =
+ homeChange: TransitionInfo.Change? = createHomeChange(),
+ ) =
TransitionInfo(type, /* flags= */ 0).apply {
homeChange?.let { addChange(it) }
addChange( // Dragged Task.
@@ -741,11 +750,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
}
- private fun createHomeChange() = TransitionInfo.Change(mock(), homeTaskLeash).apply {
- parent = null
- taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- flags = flags or FLAG_IS_WALLPAPER
- }
+ private fun createHomeChange() =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
private fun systemPropertiesKey(name: String) =
"${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index b50af741b2a6..439be9155b26 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -17,9 +17,13 @@
package com.android.wm.shell.recents;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX;
import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING;
import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING;
import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED;
@@ -44,9 +48,11 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -57,6 +63,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.IResultReceiver;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -92,9 +99,13 @@ import java.util.Optional;
@SmallTest
public class RecentsTransitionHandlerTest extends ShellTestCase {
+ private static final int FREEFORM_TASK_CORNER_RADIUS = 32;
+
@Mock
private Context mContext;
@Mock
+ private Resources mResources;
+ @Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
private ShellCommandHandler mShellCommandHandler;
@@ -134,6 +145,10 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getSystemService(KeyguardManager.class))
.thenReturn(mock(KeyguardManager.class));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getDimensionPixelSize(
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ ).thenReturn(FREEFORM_TASK_CORNER_RADIUS);
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
mDisplayInsetsController, mMainExecutor));
@@ -276,6 +291,57 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING);
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ public void testMergeAndFinish_openingFreeformTasks_setsCornerRadius() {
+ ActivityManager.RunningTaskInfo freeformTask =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, freeformTask)
+ .build();
+ SurfaceControl leash = mergeTransitionInfo.getChanges().get(0).getLeash();
+ final IBinder transition = startRecentsTransition(/* synthetic= */ false);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mRecentsTransitionHandler.startAnimation(
+ transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mRecentsTransitionHandler.findController(transition).merge(
+ mergeTransitionInfo,
+ new StubTransaction(),
+ finishT,
+ transition,
+ mock(Transitions.TransitionFinishCallback.class));
+ mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false,
+ false /* sendUserLeaveHint */, mock(IResultReceiver.class));
+ mMainExecutor.flushAll();
+
+ verify(finishT).setCornerRadius(leash, FREEFORM_TASK_CORNER_RADIUS);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+ public void testFinish_returningToFreeformTasks_setsCornerRadius() {
+ ActivityManager.RunningTaskInfo freeformTask =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ TransitionInfo transitionInfo = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_CLOSE, freeformTask)
+ .build();
+ SurfaceControl leash = transitionInfo.getChanges().get(0).getLeash();
+ final IBinder transition = startRecentsTransition(/* synthetic= */ false);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mRecentsTransitionHandler.startAnimation(
+ transition, transitionInfo, new StubTransaction(), finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false,
+ false /* sendUserLeaveHint */, mock(IResultReceiver.class));
+ mMainExecutor.flushAll();
+
+
+ verify(finishT).setCornerRadius(leash, FREEFORM_TASK_CORNER_RADIUS);
+ }
+
private IBinder startRecentsTransition(boolean synthetic) {
return startRecentsTransition(synthetic, mock(IRecentsAnimationRunner.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index 33f14acd0f02..391d46287498 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -157,33 +157,33 @@ class DesktopModeStatusTest : ShellTestCase() {
}
@Test
- fun isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
- doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ fun isInternalDisplayEligibleToHostDesktops_configDEModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops))
- assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
- fun isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
- assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ fun isInternalDisplayEligibleToHostDesktops_supportFlagOff_returnsFalse() {
+ assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
- fun isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
- assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ fun isInternalDisplayEligibleToHostDesktops_supportFlagOn_returnsFalse() {
+ assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isFalse()
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
- fun isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+ fun isInternalDisplayEligibleToHostDesktops_supportFlagOn_configDevOptModeOn_returnsTrue() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
- assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ assertThat(DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mockContext)).isTrue()
}
@DisableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index b9d6a454694d..e5a6a6d258dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -360,7 +360,8 @@ public class SplitTransitionTests extends ShellTestCase {
mStageCoordinator.onRecentsInSplitAnimationFinishing(false /* returnToApp */, commitWCT,
mock(SurfaceControl.Transaction.class));
} else {
- mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mStageCoordinator.onRecentsInSplitAnimationFinishing(
+ mStageCoordinator.wctIsReorderingSplitToTop(commitWCT), commitWCT,
mock(SurfaceControl.Transaction.class));
}
assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -430,7 +431,8 @@ public class SplitTransitionTests extends ShellTestCase {
mStageCoordinator.onRecentsInSplitAnimationFinishing(true /* returnToApp */, restoreWCT,
mock(SurfaceControl.Transaction.class));
} else {
- mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mStageCoordinator.onRecentsInSplitAnimationFinishing(
+ mStageCoordinator.wctIsReorderingSplitToTop(restoreWCT), restoreWCT,
mock(SurfaceControl.Transaction.class));
}
assertTrue(mStageCoordinator.isSplitScreenVisible());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index 53ae967e7bbf..067dcec5d65d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -73,7 +73,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(false).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
+ doReturn(false).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
doReturn(true).`when` { DesktopModeStatus.overridesShowAppHandle(any())}
setUpCommon()
whenever(mockDisplayController.getDisplay(anyInt())).thenReturn(mockDisplay)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index f15418adf1e3..49812d381178 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -116,7 +116,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(Mockito.any()) }
+ doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(Mockito.any()) }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(Mockito.any()) }
doReturn(false).`when` { DesktopModeStatus.overridesShowAppHandle(Mockito.any()) }
setUpCommon()
@@ -384,7 +385,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
+ doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(any()) }
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index b1550b0b6888..63a024b8e780 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -260,7 +260,7 @@ sk_sp<Bitmap> Bitmap::createFrom(AHardwareBuffer* hardwareBuffer, const SkImageI
#endif
sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr,
- size_t size, bool readOnly) {
+ size_t size, bool readOnly, int64_t id) {
#ifdef _WIN32 // ashmem not implemented on Windows
return nullptr;
#else
@@ -279,7 +279,7 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, size_t rowBytes, int f
}
}
- sk_sp<Bitmap> bitmap(new Bitmap(addr, fd, size, info, rowBytes));
+ sk_sp<Bitmap> bitmap(new Bitmap(addr, fd, size, info, rowBytes, id));
if (readOnly) {
bitmap->setImmutable();
}
@@ -334,7 +334,7 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info
: SkPixelRef(info.width(), info.height(), address, rowBytes)
, mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Ashmem)
- , mId(id != INVALID_BITMAP_ID ? id : getId(mPixelStorageType)) {
+ , mId(id != UNDEFINED_BITMAP_ID ? id : getId(mPixelStorageType)) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 8abe6a8c445a..4e9bcf27c0ef 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -97,7 +97,7 @@ public:
BitmapPalette palette);
#endif
static sk_sp<Bitmap> createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr,
- size_t size, bool readOnly);
+ size_t size, bool readOnly, int64_t id);
static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&);
int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); }
@@ -183,15 +183,15 @@ public:
static bool compress(const SkBitmap& bitmap, JavaCompressFormat format,
int32_t quality, SkWStream* stream);
-private:
- static constexpr uint64_t INVALID_BITMAP_ID = 0u;
+ static constexpr uint64_t UNDEFINED_BITMAP_ID = 0u;
+private:
static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes);
Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info);
Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes,
- uint64_t id = INVALID_BITMAP_ID);
+ uint64_t id = UNDEFINED_BITMAP_ID);
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes,
BitmapPalette palette);
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 29efd98b41d0..cfde0b28c0d5 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -191,9 +191,8 @@ void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
info.width(), info.height(), isPremultiplied);
}
-jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
- int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
- int density) {
+jobject createBitmap(JNIEnv* env, Bitmap* bitmap, int bitmapCreateFlags, jbyteArray ninePatchChunk,
+ jobject ninePatchInsets, int density, int64_t id) {
static jmethodID gBitmap_constructorMethodID =
GetMethodIDOrDie(env, gBitmap_class,
"<init>", "(JJIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
@@ -208,10 +207,12 @@ jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
if (!isMutable) {
bitmapWrapper->bitmap().setImmutable();
}
+ int64_t bitmapId = id != Bitmap::UNDEFINED_BITMAP_ID ? id : bitmap->getId();
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- static_cast<jlong>(bitmap->getId()), reinterpret_cast<jlong>(bitmapWrapper),
- bitmap->width(), bitmap->height(), density,
- isPremultiplied, ninePatchChunk, ninePatchInsets, fromMalloc);
+ static_cast<jlong>(bitmapId),
+ reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(),
+ bitmap->height(), density, isPremultiplied, ninePatchChunk,
+ ninePatchInsets, fromMalloc);
if (env->ExceptionCheck() != 0) {
ALOGE("*** Uncaught exception returned from Java call!\n");
@@ -759,6 +760,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
const int32_t height = p.readInt32();
const int32_t rowBytes = p.readInt32();
const int32_t density = p.readInt32();
+ const int64_t sourceId = p.readInt64();
if (kN32_SkColorType != colorType &&
kRGBA_F16_SkColorType != colorType &&
@@ -815,7 +817,8 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
return STATUS_NO_MEMORY;
}
nativeBitmap =
- Bitmap::createFrom(imageInfo, rowBytes, fd.release(), addr, size, !isMutable);
+ Bitmap::createFrom(imageInfo, rowBytes, fd.release(), addr, size,
+ !isMutable, sourceId);
return STATUS_OK;
});
@@ -831,15 +834,15 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
}
return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable), nullptr,
- nullptr, density);
+ nullptr, density, sourceId);
#else
jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
return NULL;
#endif
}
-static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
- jlong bitmapHandle, jint density, jobject parcel) {
+static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density,
+ jobject parcel) {
#ifdef __ANDROID__ // Layoutlib does not support parcel
if (parcel == NULL) {
ALOGD("------- writeToParcel null parcel\n");
@@ -870,6 +873,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
binder_status_t status;
int fd = bitmapWrapper->bitmap().getAshmemFd();
if (fd >= 0 && p.allowFds() && bitmap.isImmutable()) {
+ p.writeInt64(bitmapWrapper->bitmap().getId());
#if DEBUG_PARCEL
ALOGD("Bitmap.writeToParcel: transferring immutable bitmap's ashmem fd as "
"immutable blob (fds %s)",
@@ -889,7 +893,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
ALOGD("Bitmap.writeToParcel: copying bitmap into new blob (fds %s)",
p.allowFds() ? "allowed" : "forbidden");
#endif
-
+ p.writeInt64(Bitmap::UNDEFINED_BITMAP_ID);
status = writeBlob(p.get(), bitmapWrapper->bitmap().getId(), bitmap);
if (status) {
doThrowRE(env, "Could not copy bitmap to parcel blob.");
diff --git a/libs/hwui/jni/Bitmap.h b/libs/hwui/jni/Bitmap.h
index 21a93f066d9b..c93246a972b6 100644
--- a/libs/hwui/jni/Bitmap.h
+++ b/libs/hwui/jni/Bitmap.h
@@ -18,6 +18,7 @@
#include <jni.h>
#include <android/bitmap.h>
+#include <hwui/Bitmap.h>
struct SkImageInfo;
@@ -33,9 +34,9 @@ enum BitmapCreateFlags {
kBitmapCreateFlag_Premultiplied = 0x2,
};
-jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
- int bitmapCreateFlags, jbyteArray ninePatchChunk = nullptr,
- jobject ninePatchInsets = nullptr, int density = -1);
+jobject createBitmap(JNIEnv* env, Bitmap* bitmap, int bitmapCreateFlags,
+ jbyteArray ninePatchChunk = nullptr, jobject ninePatchInsets = nullptr,
+ int density = -1, int64_t id = Bitmap::UNDEFINED_BITMAP_ID);
Bitmap& toBitmap(jlong bitmapHandle);
diff --git a/libs/hwui/jni/ScopedParcel.cpp b/libs/hwui/jni/ScopedParcel.cpp
index b0f5423813b7..95e4e01d8df8 100644
--- a/libs/hwui/jni/ScopedParcel.cpp
+++ b/libs/hwui/jni/ScopedParcel.cpp
@@ -39,6 +39,16 @@ uint32_t ScopedParcel::readUint32() {
return temp;
}
+int64_t ScopedParcel::readInt64() {
+ int64_t temp = 0;
+ // TODO: This behavior-matches what android::Parcel does
+ // but this should probably be better
+ if (AParcel_readInt64(mParcel, &temp) != STATUS_OK) {
+ temp = 0;
+ }
+ return temp;
+}
+
float ScopedParcel::readFloat() {
float temp = 0.;
if (AParcel_readFloat(mParcel, &temp) != STATUS_OK) {
diff --git a/libs/hwui/jni/ScopedParcel.h b/libs/hwui/jni/ScopedParcel.h
index fd8d6a210f0f..f2f138fda43c 100644
--- a/libs/hwui/jni/ScopedParcel.h
+++ b/libs/hwui/jni/ScopedParcel.h
@@ -35,12 +35,16 @@ public:
uint32_t readUint32();
+ int64_t readInt64();
+
float readFloat();
void writeInt32(int32_t value) { AParcel_writeInt32(mParcel, value); }
void writeUint32(uint32_t value) { AParcel_writeUint32(mParcel, value); }
+ void writeInt64(int64_t value) { AParcel_writeInt64(mParcel, value); }
+
void writeFloat(float value) { AParcel_writeFloat(mParcel, value); }
bool allowFds() const { return AParcel_getAllowFds(mParcel); }
diff --git a/media/java/android/media/audiofx/HapticGenerator.java b/media/java/android/media/audiofx/HapticGenerator.java
index d2523ef43b9e..7f94ddea9b84 100644
--- a/media/java/android/media/audiofx/HapticGenerator.java
+++ b/media/java/android/media/audiofx/HapticGenerator.java
@@ -36,6 +36,20 @@ import java.util.UUID;
* <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
* effects.
+ *
+ * <pre>{@code
+ * AudioManager audioManager = context.getSystemService(AudioManager.class);
+ * player = MediaPlayer.create(
+ * context,
+ * audioUri,
+ * new AudioAttributes.Builder().setHapticChannelsMuted(false).build(),
+ * audioManager.generateAudioSessionId()
+ * );
+ * if (HapticGenerator.isAvailable()) {
+ * HapticGenerator.create(player.getAudioSessionId()).setEnabled(true);
+ * }
+ * player.start();
+ * }</pre>
*/
public class HapticGenerator extends AudioEffect implements AutoCloseable {
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index ec287c1b65b7..52a2160cdd74 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -95,6 +95,8 @@ message PreferenceProto {
optional PermissionsProto write_permissions = 18;
// Tag constants associated with the preference.
repeated string tags = 19;
+ // Permit to read and write preference value (the lower 15 bits is reserved for read permit).
+ optional int32 read_write_permit = 20;
// Target of an Intent
message ActionTarget {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index e511bf1c175d..13541b1ebc9a 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -56,6 +56,8 @@ import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.metadata.ReadWritePermit
+import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY
+import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -415,52 +417,46 @@ fun PreferenceMetadata.toProto(
for (tag in metadata.tags(context)) addTags(tag)
}
persistent = metadata.isPersistent(context)
- if (persistent) {
- if (metadata is PersistentPreference<*>) {
- sensitivityLevel = metadata.sensitivityLevel
- metadata.getReadPermissions(context)?.let {
- if (it.size > 0) readPermissions = it.toProto()
- }
- metadata.getWritePermissions(context)?.let {
- if (it.size > 0) writePermissions = it.toProto()
+ if (metadata !is PersistentPreference<*>) return@preferenceProto
+ sensitivityLevel = metadata.sensitivityLevel
+ metadata.getReadPermissions(context)?.let { if (it.size > 0) readPermissions = it.toProto() }
+ metadata.getWritePermissions(context)?.let { if (it.size > 0) writePermissions = it.toProto() }
+ val readPermit = metadata.evalReadPermit(context, callingPid, callingUid)
+ val writePermit =
+ metadata.evalWritePermit(context, callingPid, callingUid) ?: ReadWritePermit.ALLOW
+ readWritePermit = ReadWritePermit.make(readPermit, writePermit)
+ if (
+ flags.includeValue() &&
+ enabled &&
+ (!hasAvailable() || available) &&
+ (!hasRestricted() || !restricted) &&
+ readPermit == ReadWritePermit.ALLOW
+ ) {
+ val storage = metadata.storage(context)
+ value = preferenceValueProto {
+ when (metadata.valueType) {
+ Int::class.javaObjectType -> storage.getInt(metadata.key)?.let { intValue = it }
+ Boolean::class.javaObjectType ->
+ storage.getBoolean(metadata.key)?.let { booleanValue = it }
+ Float::class.javaObjectType ->
+ storage.getFloat(metadata.key)?.let { floatValue = it }
+ else -> {}
}
}
- if (
- flags.includeValue() &&
- enabled &&
- (!hasAvailable() || available) &&
- (!hasRestricted() || !restricted) &&
- metadata is PersistentPreference<*> &&
- metadata.evalReadPermit(context, callingPid, callingUid) == ReadWritePermit.ALLOW
- ) {
- val storage = metadata.storage(context)
- value = preferenceValueProto {
- when (metadata.valueType) {
- Int::class.javaObjectType -> storage.getInt(metadata.key)?.let { intValue = it }
- Boolean::class.javaObjectType ->
- storage.getBoolean(metadata.key)?.let { booleanValue = it }
- Float::class.javaObjectType ->
- storage.getFloat(metadata.key)?.let { floatValue = it }
- else -> {}
- }
- }
- }
- if (flags.includeValueDescriptor()) {
- valueDescriptor = preferenceValueDescriptorProto {
- when (metadata) {
- is IntRangeValuePreference -> rangeValue = rangeValueProto {
- min = metadata.getMinValue(context)
- max = metadata.getMaxValue(context)
- step = metadata.getIncrementStep(context)
- }
- else -> {}
- }
- if (metadata is PersistentPreference<*>) {
- when (metadata.valueType) {
- Boolean::class.javaObjectType -> booleanType = true
- Float::class.javaObjectType -> floatType = true
+ }
+ if (flags.includeValueDescriptor()) {
+ valueDescriptor = preferenceValueDescriptorProto {
+ when (metadata) {
+ is IntRangeValuePreference -> rangeValue = rangeValueProto {
+ min = metadata.getMinValue(context)
+ max = metadata.getMaxValue(context)
+ step = metadata.getIncrementStep(context)
}
- }
+ else -> {}
+ }
+ when (metadata.valueType) {
+ Boolean::class.javaObjectType -> booleanType = true
+ Float::class.javaObjectType -> floatType = true
}
}
}
@@ -478,6 +474,20 @@ fun <T> PersistentPreference<T>.evalReadPermit(
else -> getReadPermit(context, callingPid, callingUid)
}
+/** Evaluates the write permit of a persistent preference. */
+fun <T> PersistentPreference<T>.evalWritePermit(
+ context: Context,
+ callingPid: Int,
+ callingUid: Int,
+): Int? =
+ when {
+ sensitivityLevel == UNKNOWN_SENSITIVITY || sensitivityLevel == HIGH_SENSITIVITY ->
+ ReadWritePermit.DISALLOW
+ getWritePermissions(context)?.check(context, callingPid, callingUid) == false ->
+ ReadWritePermit.REQUIRE_APP_PERMISSION
+ else -> getWritePermit(context, callingPid, callingUid)
+ }
+
private fun PreferenceMetadata.getTitleTextProto(context: Context, isRoot: Boolean): TextProto? {
if (isRoot && this is PreferenceScreenMetadata) {
val titleRes = screenTitle
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index 60f9c6bb92a3..72f6934b5f35 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -36,8 +36,6 @@ import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.ReadWritePermit
-import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY
-import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
/** Request to set preference value. */
class PreferenceSetterRequest(
@@ -223,13 +221,8 @@ fun <T> PersistentPreference<T>.evalWritePermit(
callingPid: Int,
callingUid: Int,
): Int =
- when {
- sensitivityLevel == UNKNOWN_SENSITIVITY || sensitivityLevel == HIGH_SENSITIVITY ->
- ReadWritePermit.DISALLOW
- getWritePermissions(context)?.check(context, callingPid, callingUid) == false ->
- ReadWritePermit.REQUIRE_APP_PERMISSION
- else -> getWritePermit(context, value, callingPid, callingUid)
- }
+ evalWritePermit(context, callingPid, callingUid)
+ ?: getWritePermit(context, value, callingPid, callingUid)
/** Message codec for [PreferenceSetterRequest]. */
object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> {
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index e456a7f1aa1c..c723dce82b5a 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -41,6 +41,19 @@ annotation class ReadWritePermit {
const val REQUIRE_APP_PERMISSION = 2
/** Require explicit user agreement (e.g. terms of service). */
const val REQUIRE_USER_AGREEMENT = 3
+
+ private const val READ_PERMIT_BITS = 15
+ private const val READ_PERMIT_MASK = (1 shl 16) - 1
+
+ /** Wraps given read and write permit into an integer. */
+ fun make(readPermit: @ReadWritePermit Int, writePermit: @ReadWritePermit Int): Int =
+ (writePermit shl READ_PERMIT_BITS) or readPermit
+
+ /** Extracts the read permit from given integer generated by [make]. */
+ fun getReadPermit(readWritePermit: Int): Int = readWritePermit and READ_PERMIT_MASK
+
+ /** Extracts the write permit from given integer generated by [make]. */
+ fun getWritePermit(readWritePermit: Int): Int = readWritePermit shr READ_PERMIT_BITS
}
}
@@ -81,6 +94,12 @@ interface PersistentPreference<T> : PreferenceMetadata {
/** The value type the preference is associated with. */
val valueType: Class<T>
+ /** The sensitivity level of the preference. */
+ val sensitivityLevel: @SensitivityLevel Int
+ get() = SensitivityLevel.UNKNOWN_SENSITIVITY
+
+ override fun isPersistent(context: Context) = true
+
/**
* Returns the key-value storage of the preference.
*
@@ -102,19 +121,27 @@ interface PersistentPreference<T> : PreferenceMetadata {
* behind the scene.
*/
fun getReadPermit(context: Context, callingPid: Int, callingUid: Int): @ReadWritePermit Int =
- PreferenceScreenRegistry.getReadPermit(
- context,
- callingPid,
- callingUid,
- this,
- )
+ PreferenceScreenRegistry.defaultReadPermit
/** Returns the required permissions to write preference value. */
fun getWritePermissions(context: Context): Permissions? = null
/**
* Returns if the external application (identified by [callingPid] and [callingUid]) is
- * permitted to write preference with given [value].
+ * permitted to write preference value. If the write permit depends on certain value, implement
+ * the overloading [getWritePermit] instead.
+ *
+ * The underlying implementation does NOT need to check common states like isEnabled,
+ * isRestricted, isAvailable or permissions in [getWritePermissions]. The framework will do it
+ * behind the scene.
+ */
+ fun getWritePermit(context: Context, callingPid: Int, callingUid: Int): @ReadWritePermit Int? =
+ null
+
+ /**
+ * Returns if the external application (identified by [callingPid] and [callingUid]) is
+ * permitted to write preference with given [value]. Note that if the overloading
+ * [getWritePermit] returns non null value, this method will be ignored!
*
* The underlying implementation does NOT need to check common states like isEnabled,
* isRestricted, isAvailable or permissions in [getWritePermissions]. The framework will do it
@@ -125,18 +152,7 @@ interface PersistentPreference<T> : PreferenceMetadata {
value: T?,
callingPid: Int,
callingUid: Int,
- ): @ReadWritePermit Int =
- PreferenceScreenRegistry.getWritePermit(
- context,
- value,
- callingPid,
- callingUid,
- this,
- )
-
- /** The sensitivity level of the preference. */
- val sensitivityLevel: @SensitivityLevel Int
- get() = SensitivityLevel.UNKNOWN_SENSITIVITY
+ ): @ReadWritePermit Int = PreferenceScreenRegistry.defaultWritePermit
}
/** Descriptor of values. */
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 a8939ab0d902..7f2a61081fbb 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -127,7 +127,7 @@ interface PreferenceMetadata {
fun dependencies(context: Context): Array<String> = arrayOf()
/** Returns if the preference is persistent in datastore. */
- fun isPersistent(context: Context): Boolean = this is PersistentPreference<*>
+ fun isPersistent(context: Context): Boolean = false
/**
* Returns if preference value backup is allowed (by default returns `true` if preference is
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
index 246310984db9..8d4bfffb1fdb 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
@@ -22,12 +22,18 @@ import android.util.Log
import com.android.settingslib.datastore.KeyValueStore
/** Registry of all available preference screens in the app. */
-object PreferenceScreenRegistry : ReadWritePermitProvider {
+object PreferenceScreenRegistry {
private const val TAG = "ScreenRegistry"
/** Provider of key-value store. */
private lateinit var keyValueStoreProvider: KeyValueStoreProvider
+ /** The default permit for external application to read preference values. */
+ var defaultReadPermit: @ReadWritePermit Int = ReadWritePermit.DISALLOW
+
+ /** The default permit for external application to write preference values. */
+ var defaultWritePermit: @ReadWritePermit Int = ReadWritePermit.DISALLOW
+
/**
* Factories of all available [PreferenceScreenMetadata]s.
*
@@ -38,9 +44,6 @@ object PreferenceScreenRegistry : ReadWritePermitProvider {
/** Metrics logger for preference actions triggered by user interaction. */
var preferenceUiActionMetricsLogger: PreferenceUiActionMetricsLogger? = null
- private var readWritePermitProvider: ReadWritePermitProvider =
- object : ReadWritePermitProvider {}
-
/** Sets the [KeyValueStoreProvider]. */
fun setKeyValueStoreProvider(keyValueStoreProvider: KeyValueStoreProvider) {
this.keyValueStoreProvider = keyValueStoreProvider
@@ -77,28 +80,6 @@ object PreferenceScreenRegistry : ReadWritePermitProvider {
return null
}
}
-
- /**
- * Sets the provider to check read write permit. Read and write requests are denied by default.
- */
- fun setReadWritePermitProvider(readWritePermitProvider: ReadWritePermitProvider) {
- this.readWritePermitProvider = readWritePermitProvider
- }
-
- override fun getReadPermit(
- context: Context,
- callingPid: Int,
- callingUid: Int,
- preference: PreferenceMetadata,
- ) = readWritePermitProvider.getReadPermit(context, callingPid, callingUid, preference)
-
- override fun getWritePermit(
- context: Context,
- value: Any?,
- callingPid: Int,
- callingUid: Int,
- preference: PreferenceMetadata,
- ) = readWritePermitProvider.getWritePermit(context, value, callingPid, callingUid, preference)
}
/** Provider of [KeyValueStore]. */
@@ -113,25 +94,3 @@ fun interface KeyValueStoreProvider {
*/
fun getKeyValueStore(context: Context, preference: PreferenceMetadata): KeyValueStore?
}
-
-/** Provider of read and write permit. */
-interface ReadWritePermitProvider {
-
- val defaultReadWritePermit: @ReadWritePermit Int
- get() = ReadWritePermit.DISALLOW
-
- fun getReadPermit(
- context: Context,
- callingPid: Int,
- callingUid: Int,
- preference: PreferenceMetadata,
- ): @ReadWritePermit Int = defaultReadWritePermit
-
- fun getWritePermit(
- context: Context,
- value: Any?,
- callingPid: Int,
- callingUid: Int,
- preference: PreferenceMetadata,
- ): @ReadWritePermit Int = defaultReadWritePermit
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index bf86911ee683..572444edea29 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -30,11 +30,13 @@ import android.util.Log;
import androidx.annotation.ChecksSdkIntAtLeast;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -385,7 +387,7 @@ public class CsipDeviceManager {
preferredMainDevice.refresh();
hasChanged = true;
}
- syncAudioSharingSourceIfNeeded(preferredMainDevice);
+ syncAudioSharingStatusIfNeeded(preferredMainDevice);
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -399,13 +401,16 @@ public class CsipDeviceManager {
return userManager != null && userManager.isManagedProfile();
}
- private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
+ private void syncAudioSharingStatusIfNeeded(CachedBluetoothDevice mainDevice) {
boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
- if (isAudioSharingEnabled) {
+ if (isAudioSharingEnabled && mainDevice != null) {
if (isWorkProfile()) {
- log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, work profile");
return;
}
+ Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
+ deviceSet.add(mainDevice);
+ deviceSet.addAll(mainDevice.getMemberDevice());
boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
&& BluetoothUtils.hasConnectedBroadcastSource(
mainDevice, mBtManager);
@@ -419,9 +424,6 @@ public class CsipDeviceManager {
if (metadata != null && assistant != null) {
log("addMemberDevicesIntoMainDevice: sync audio sharing source after "
+ "combining the top level devices.");
- Set<CachedBluetoothDevice> deviceSet = new HashSet<>();
- deviceSet.add(mainDevice);
- deviceSet.addAll(mainDevice.getMemberDevice());
Set<BluetoothDevice> sinksToSync = deviceSet.stream()
.map(CachedBluetoothDevice::getDevice)
.filter(device ->
@@ -435,8 +437,24 @@ public class CsipDeviceManager {
}
}
}
+ if (Flags.enableTemporaryBondDevicesUi()) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for audio sharing "
+ + "sinks after combining the top level devices.");
+ Set<BluetoothDevice> sinksToSync = deviceSet.stream()
+ .map(CachedBluetoothDevice::getDevice).filter(Objects::nonNull).collect(
+ Collectors.toSet());
+ if (sinksToSync.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice)) {
+ for (BluetoothDevice device : sinksToSync) {
+ if (!BluetoothUtils.isTemporaryBondDevice(device)) {
+ log("addMemberDevicesIntoMainDevice: sync temp bond metadata for "
+ + device.getAnonymizedAddress());
+ BluetoothUtils.setTemporaryBondMetadata(device);
+ }
+ }
+ }
+ }
} else {
- log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
+ log("addMemberDevicesIntoMainDevice: skip sync audio sharing status, flag disabled");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
index 10156c404ebf..bac564c7d0f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.kt
@@ -20,6 +20,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaMetadata
import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
@@ -98,16 +99,22 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
}
/** Set volume `level` to remote media `token` */
- fun setVolume(token: MediaSession.Token, level: Int) {
+ fun setVolume(sessionId: SessionId, volumeLevel: Int) {
+ when (sessionId) {
+ is SessionId.Media -> setMediaSessionVolume(sessionId.token, volumeLevel)
+ }
+ }
+
+ private fun setMediaSessionVolume(token: MediaSession.Token, volumeLevel: Int) {
val record = mRecords[token]
if (record == null) {
Log.w(TAG, "setVolume: No record found for token $token")
return
}
if (D.BUG) {
- Log.d(TAG, "Setting level to $level")
+ Log.d(TAG, "Setting level to $volumeLevel")
}
- record.controller.setVolumeTo(level, 0)
+ record.controller.setVolumeTo(volumeLevel, 0)
}
private fun onRemoteVolumeChangedH(sessionToken: MediaSession.Token, flags: Int) {
@@ -122,7 +129,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
)
}
val token = controller.sessionToken
- mCallbacks.onRemoteVolumeChanged(token, flags)
+ mCallbacks.onRemoteVolumeChanged(SessionId.from(token), flags)
}
private fun onUpdateRemoteSessionListH(sessionToken: MediaSession.Token?) {
@@ -158,7 +165,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
controller.registerCallback(record, mHandler)
}
val record = mRecords[token]
- val remote = isRemote(playbackInfo)
+ val remote = playbackInfo.isRemote()
if (remote) {
updateRemoteH(token, record!!.name, playbackInfo)
record.sentRemote = true
@@ -172,7 +179,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
Log.d(TAG, "Removing " + record.name + " sentRemote=" + record.sentRemote)
}
if (record.sentRemote) {
- mCallbacks.onRemoteRemoved(token)
+ mCallbacks.onRemoteRemoved(SessionId.from(token))
record.sentRemote = false
}
}
@@ -213,8 +220,8 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
private fun updateRemoteH(
token: MediaSession.Token,
name: String?,
- pi: MediaController.PlaybackInfo,
- ) = mCallbacks.onRemoteUpdate(token, name, pi)
+ playbackInfo: PlaybackInfo,
+ ) = mCallbacks.onRemoteUpdate(SessionId.from(token), name, VolumeInfo.from(playbackInfo))
private inner class MediaControllerRecord(val controller: MediaController) :
MediaController.Callback() {
@@ -225,7 +232,7 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
return method + " " + controller.packageName + " "
}
- override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+ override fun onAudioInfoChanged(info: PlaybackInfo) {
if (D.BUG) {
Log.d(
TAG,
@@ -235,9 +242,9 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
sentRemote),
)
}
- val remote = isRemote(info)
+ val remote = info.isRemote()
if (!remote && sentRemote) {
- mCallbacks.onRemoteRemoved(controller.sessionToken)
+ mCallbacks.onRemoteRemoved(SessionId.from(controller.sessionToken))
sentRemote = false
} else if (remote) {
updateRemoteH(controller.sessionToken, name, info)
@@ -301,20 +308,36 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
}
}
+ /** Opaque id for ongoing sessions that support volume adjustment. */
+ sealed interface SessionId {
+
+ companion object {
+ fun from(token: MediaSession.Token) = Media(token)
+ }
+
+ data class Media(val token: MediaSession.Token) : SessionId
+ }
+
+ /** Holds session volume information. */
+ data class VolumeInfo(val currentVolume: Int, val maxVolume: Int) {
+
+ companion object {
+
+ fun from(playbackInfo: PlaybackInfo) =
+ VolumeInfo(playbackInfo.currentVolume, playbackInfo.maxVolume)
+ }
+ }
+
/** Callback for remote media sessions */
interface Callbacks {
/** Invoked when remote media session is updated */
- fun onRemoteUpdate(
- token: MediaSession.Token?,
- name: String?,
- pi: MediaController.PlaybackInfo?,
- )
+ fun onRemoteUpdate(token: SessionId?, name: String?, volumeInfo: VolumeInfo?)
/** Invoked when remote media session is removed */
- fun onRemoteRemoved(token: MediaSession.Token?)
+ fun onRemoteRemoved(token: SessionId?)
/** Invoked when remote volume is changed */
- fun onRemoteVolumeChanged(token: MediaSession.Token?, flags: Int)
+ fun onRemoteVolumeChanged(token: SessionId?, flags: Int)
}
companion object {
@@ -325,12 +348,11 @@ class MediaSessions(context: Context, looper: Looper, callbacks: Callbacks) {
const val UPDATE_REMOTE_SESSION_LIST: Int = 3
private const val USE_SERVICE_LABEL = false
-
- private fun isRemote(pi: MediaController.PlaybackInfo?): Boolean =
- pi != null && pi.playbackType == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
}
}
+private fun PlaybackInfo?.isRemote() = this?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+
private fun MediaController.dump(n: Int, writer: PrintWriter) {
writer.println(" Controller $n: $packageName")
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index fd14d1ff6786..2eccaa626f3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -40,6 +40,8 @@ import android.content.Context;
import android.os.Looper;
import android.os.Parcel;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.flags.Flags;
@@ -74,6 +76,9 @@ public class CsipDeviceManagerTest {
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+ private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+ private static final String TEMP_BOND_METADATA =
+ "<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private final static int GROUP1 = 1;
private final BluetoothClass DEVICE_CLASS_1 =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
@@ -337,6 +342,7 @@ public class CsipDeviceManagerTest {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
@@ -346,7 +352,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -359,6 +364,7 @@ public class CsipDeviceManagerTest {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void
addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
// Condition: The preferredDevice is main and there is another main device in top list
@@ -369,7 +375,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -377,6 +382,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mUserManager.isManagedProfile()).thenReturn(true);
@@ -387,10 +394,13 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncState() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice1;
@@ -399,7 +409,6 @@ public class CsipDeviceManagerTest {
mCachedDevices.add(preferredDevice);
mCachedDevices.add(mCachedDevice2);
mCachedDevices.add(mCachedDevice3);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -407,6 +416,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -415,6 +426,8 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
verify(mAssistant).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ verify(mDevice1).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
@@ -436,13 +449,13 @@ public class CsipDeviceManagerTest {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(false);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
@@ -457,16 +470,20 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant, never()).addSource(any(BluetoothDevice.class),
any(BluetoothLeBroadcastMetadata.class), anyBoolean());
+ verify(mDevice2, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3, never()).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
- public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncSource() {
+ @EnableFlags({Flags.FLAG_ENABLE_LE_AUDIO_SHARING, Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI})
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_syncState() {
// Condition: The preferredDevice is member and there are two main device in top list
// Expected Result: return true and there is the preferredDevice in top list
CachedBluetoothDevice preferredDevice = mCachedDevice2;
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
mCachedDevice3.setGroupId(GROUP1);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isEnabled(null)).thenReturn(true);
BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
@@ -474,6 +491,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+ when(mDevice1.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(TEMP_BOND_METADATA.getBytes());
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -488,6 +507,10 @@ public class CsipDeviceManagerTest {
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
verify(mAssistant).addSource(mDevice2, metadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice3, metadata, /* isGroupOp= */ false);
+ verify(mDevice2).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
+ verify(mDevice3).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ TEMP_BOND_METADATA.getBytes());
}
@Test
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
index 93690d48cd04..b0fd925daec3 100644
--- a/packages/Shell/src/com/android/shell/BugreportPrefs.java
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -23,25 +23,24 @@ import android.content.SharedPreferences;
* Preferences related to bug reports.
*/
final class BugreportPrefs {
- static final String PREFS_BUGREPORT = "bugreports";
-
- private static final String KEY_WARNING_STATE = "warning-state";
-
- static final int STATE_UNKNOWN = 0;
- // Shows the warning dialog.
- static final int STATE_SHOW = 1;
- // Skips the warning dialog.
- static final int STATE_HIDE = 2;
static int getWarningState(Context context, int def) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- return prefs.getInt(KEY_WARNING_STATE, def);
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ return prefs.getInt(keyWarningState, def);
}
static void setWarningState(Context context, int value) {
- final SharedPreferences prefs = context.getSharedPreferences(
- PREFS_BUGREPORT, Context.MODE_PRIVATE);
- prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+ String prefsBugreport = context.getResources().getString(
+ com.android.internal.R.string.prefs_bugreport);
+ String keyWarningState = context.getResources().getString(
+ com.android.internal.R.string.key_warning_state);
+ final SharedPreferences prefs = context.getSharedPreferences(prefsBugreport,
+ Context.MODE_PRIVATE);
+ prefs.edit().putInt(keyWarningState, value).apply();
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 61f49db07abc..fb0678fedb56 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,8 +21,6 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.flags.Flags.handleBugreportsForWear;
@@ -1347,7 +1345,11 @@ public class BugreportProgressService extends Service {
}
private boolean hasUserDecidedNotToGetWarningMessage() {
- return getWarningState(mContext, STATE_UNKNOWN) == STATE_HIDE;
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ return getWarningState(mContext, bugreportStateUnknown) == bugreportStateHide;
}
private void maybeShowWarningMessageAndCloseNotification(int id) {
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
index a44e23603f52..0e835f91aca6 100644
--- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -16,9 +16,6 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.sendShareIntent;
@@ -69,12 +66,19 @@ public class BugreportWarningActivity extends AlertActivity
mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
- final int state = getWarningState(this, STATE_UNKNOWN);
+ int bugreportStateUnknown = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+
+ final int state = getWarningState(this, bugreportStateUnknown);
final boolean checked;
if (Build.IS_USER) {
- checked = state == STATE_HIDE; // Only checks if specifically set to.
+ checked = state == bugreportStateHide; // Only checks if specifically set to.
} else {
- checked = state != STATE_SHOW; // Checks by default.
+ checked = state != bugreportStateShow; // Checks by default.
}
mConfirmRepeat.setChecked(checked);
@@ -83,9 +87,14 @@ public class BugreportWarningActivity extends AlertActivity
@Override
public void onClick(DialogInterface dialog, int which) {
+ int bugreportStateHide = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ int bugreportStateShow = getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
if (which == AlertDialog.BUTTON_POSITIVE) {
// Remember confirm state, and launch target
- setWarningState(this, mConfirmRepeat.isChecked() ? STATE_HIDE : STATE_SHOW);
+ setWarningState(this, mConfirmRepeat.isChecked() ? bugreportStateHide
+ : bugreportStateShow);
if (mSendIntent != null) {
sendShareIntent(this, mSendIntent);
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 7bda2ea790b0..2d6abe6cdc93 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,10 +19,6 @@ package com.android.shell;
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportPrefs.PREFS_BUGREPORT;
-import static com.android.shell.BugreportPrefs.STATE_HIDE;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
import static com.android.shell.BugreportPrefs.setWarningState;
import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_REQUESTED;
@@ -201,8 +197,9 @@ public class BugreportReceiverTest {
return null;
}).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
any(), anyBoolean(), anyBoolean());
-
- setWarningState(mContext, STATE_HIDE);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
+ setWarningState(mContext, bugreportStateHide);
mUiBot.turnScreenOn();
}
@@ -469,22 +466,31 @@ public class BugreportReceiverTest {
@Test
public void testBugreportFinished_withWarningUnknownState() throws Exception {
- bugreportFinishedWithWarningTest(STATE_UNKNOWN);
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ bugreportFinishedWithWarningTest(bugreportStateUnknown);
}
@Test
public void testBugreportFinished_withWarningShowAgain() throws Exception {
- bugreportFinishedWithWarningTest(STATE_SHOW);
+ int bugreportStateShow = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_show);
+ bugreportFinishedWithWarningTest(bugreportStateShow);
}
private void bugreportFinishedWithWarningTest(Integer propertyState) throws Exception {
+ int bugreportStateUnknown = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_unknown);
+ int bugreportStateHide = mContext.getResources().getInteger(
+ com.android.internal.R.integer.bugreport_state_hide);
if (propertyState == null) {
// Clear properties
- mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE)
- .edit().clear().commit();
+ mContext.getSharedPreferences(
+ mContext.getResources().getString(com.android.internal.R.string.prefs_bugreport)
+ , Context.MODE_PRIVATE).edit().clear().commit();
// Confidence check...
- assertEquals("Did not reset properties", STATE_UNKNOWN,
- getWarningState(mContext, STATE_UNKNOWN));
+ assertEquals("Did not reset properties", bugreportStateUnknown,
+ getWarningState(mContext, bugreportStateUnknown));
} else {
setWarningState(mContext, propertyState);
}
@@ -501,7 +507,8 @@ public class BugreportReceiverTest {
// TODO: get ok and dontShowAgain from the dialog reference above
UiObject dontShowAgain =
mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm_dont_repeat));
- final boolean firstTime = propertyState == null || propertyState == STATE_UNKNOWN;
+ final boolean firstTime =
+ propertyState == null || propertyState == bugreportStateUnknown;
if (firstTime) {
if (Build.IS_USER) {
assertFalse("Checkbox should NOT be checked by default on user builds",
@@ -524,8 +531,8 @@ public class BugreportReceiverTest {
assertActionSendMultiple(extras);
// Make sure it's hidden now.
- int newState = getWarningState(mContext, STATE_UNKNOWN);
- assertEquals("Didn't change state", STATE_HIDE, newState);
+ int newState = getWarningState(mContext, bugreportStateUnknown);
+ assertEquals("Didn't change state", bugreportStateHide, newState);
}
@Test
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 028a0c6e978b..910f71276376 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1946,6 +1946,16 @@ flag {
}
flag {
+ name: "unfold_latency_tracking_fix"
+ namespace: "systemui"
+ description: "New implementation to track unfold latency that excludes broken cases"
+ bug: "390649568"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "ui_rich_ongoing_force_expanded"
namespace: "systemui"
description: "Force promoted notifications to always be expanded"
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 d7d4e1714aa6..09b8d178cc8e 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
@@ -175,7 +175,7 @@ fun ContentScope.SnoozeableHeadsUpNotificationSpace(
viewModel: NotificationsPlaceholderViewModel,
) {
- val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
+ val isSnoozable by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
var scrollOffset by remember { mutableFloatStateOf(0f) }
val headsUpInset = with(LocalDensity.current) { headsUpTopInset().toPx() }
@@ -192,7 +192,7 @@ fun ContentScope.SnoozeableHeadsUpNotificationSpace(
)
}
- val nestedScrollConnection =
+ val snoozeScrollConnection =
object : NestedScrollConnection {
override suspend fun onPreFling(available: Velocity): Velocity {
if (
@@ -206,7 +206,7 @@ fun ContentScope.SnoozeableHeadsUpNotificationSpace(
}
}
- LaunchedEffect(isHeadsUp) { scrollOffset = 0f }
+ LaunchedEffect(isSnoozable) { scrollOffset = 0f }
LaunchedEffect(scrollableState.isScrollInProgress) {
if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
@@ -230,10 +230,8 @@ fun ContentScope.SnoozeableHeadsUpNotificationSpace(
),
)
}
- .thenIf(isHeadsUp) {
- Modifier.nestedScroll(nestedScrollConnection)
- .scrollable(orientation = Orientation.Vertical, state = scrollableState)
- },
+ .thenIf(isSnoozable) { Modifier.nestedScroll(snoozeScrollConnection) }
+ .scrollable(orientation = Orientation.Vertical, state = scrollableState),
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 2e5b5b56c982..aad1276d76e5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -113,8 +113,8 @@ class DefaultClockProvider(
companion object {
// 750ms @ 120hz -> 90 frames of animation
- // In practice, 45 looks good enough
- const val NUM_CLOCK_FONT_ANIMATION_STEPS = 45
+ // In practice, 30 looks good enough and limits our memory usage
+ const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
val FLEX_TYPEFACE by lazy {
// TODO(b/364680873): Move constant to config_clockFontFamily when shipping
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt
new file mode 100644
index 000000000000..0c97750ba281
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/DeviceInactiveConditionTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceInactiveConditionTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().useUnconfinedTestDispatcher().also {
+ whenever(it.wakefulnessLifecycle.wakefulness) doReturn WAKEFULNESS_AWAKE
+ }
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ DeviceInactiveCondition(
+ applicationCoroutineScope,
+ keyguardStateController,
+ wakefulnessLifecycle,
+ keyguardUpdateMonitor,
+ keyguardInteractor,
+ JavaAdapter(applicationCoroutineScope),
+ )
+ }
+
+ @Test
+ fun asleep_conditionTrue() =
+ kosmos.runTest {
+ // Condition is false to start.
+ underTest.start()
+ assertThat(underTest.isConditionMet).isFalse()
+
+ // Condition is true when device goes to sleep.
+ sleep()
+ assertThat(underTest.isConditionMet).isTrue()
+ }
+
+ @Test
+ fun dozingAndAsleep_conditionFalse() =
+ kosmos.runTest {
+ // Condition is true when device is asleep.
+ underTest.start()
+ sleep()
+ assertThat(underTest.isConditionMet).isTrue()
+
+ // Condition turns false after doze starts.
+ fakeKeyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.UNINITIALIZED, to = DozeStateModel.DOZE)
+ )
+ assertThat(underTest.isConditionMet).isFalse()
+ }
+
+ fun Kosmos.sleep() {
+ whenever(wakefulnessLifecycle.wakefulness) doReturn WAKEFULNESS_ASLEEP
+ argumentCaptor<WakefulnessLifecycle.Observer>().apply {
+ verify(wakefulnessLifecycle).addObserver(capture())
+ firstValue.onStartedGoingToSleep()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index e13f3f12c55a..7e93f5a8c9a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -20,19 +20,26 @@ import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
@@ -56,11 +63,14 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -93,7 +103,10 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
}
}
@@ -107,6 +120,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
// Transition to DOZING and set the power interactor asleep.
kosmos.powerInteractor.setAsleepForTest()
+ kosmos.setCommunalV2ConfigEnabled(true)
runBlocking {
kosmos.transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
@@ -160,7 +174,7 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
@Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
- @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR, FLAG_GLANCEABLE_HUB_V2)
fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
kosmos.runTest {
whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
@@ -179,7 +193,17 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
clearInvocations(fakeCommunalSceneRepository)
powerInteractor.setAwakeForTest()
@@ -240,7 +264,17 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
kosmos.runTest {
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device turns on.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 8e1068226431..5882cff74eb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -19,14 +19,20 @@ package com.android.systemui.keyguard.domain.interactor
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -46,6 +52,8 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
@@ -66,7 +74,10 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_SCENE_KTF_REFACTOR,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
.andSceneContainer()
}
}
@@ -101,6 +112,7 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
)
reset(kosmos.transitionRepository)
kosmos.setCommunalAvailable(true)
+ kosmos.setCommunalV2ConfigEnabled(true)
}
kosmos.underTest.start()
}
@@ -202,7 +214,17 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
reset(transitionRepository)
setCommunalAvailable(true)
- whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ if (glanceableHubV2()) {
+ val user = fakeUserRepository.asMainUser()
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ user.id,
+ )
+ batteryRepository.fake.setDevicePluggedIn(true)
+ } else {
+ whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ }
// Device wakes up.
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index 264eda5a07eb..668c606677ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.userFileManager
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -76,11 +77,11 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
@Test
fun setLargeTilesSpecs_inSharedPreferences() {
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
}
@@ -92,12 +93,12 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
val setA = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setA.toTileSpecs())
+ underTest.writeLargeTileSpecs(setA.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setA)
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
val setB = setOf("tileA", "tileB")
- underTest.setLargeTilesSpecs(setB.toTileSpecs())
+ underTest.writeLargeTileSpecs(setB.toTileSpecs())
assertThat(getLargeTilesSpecsFromSharedPreferences()).isEqualTo(setB)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -106,7 +107,7 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
}
@Test
- fun setInitialTilesFromSettings_noLargeTiles_tilesSet() =
+ fun setUpgradePathFromSettings_noLargeTiles_tilesSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -117,14 +118,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
- underTest.setInitialLargeTilesSpecs(tiles, PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles)
}
}
@Test
- fun setInitialTilesFromSettings_alreadyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_alreadyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -133,14 +137,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(setOf("tileC"))
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf("tileC").toTileSpecs())
}
}
@Test
- fun setInitialTilesFromSettings_emptyLargeTiles_tilesNotSet() =
+ fun setUpgradePathFromSettings_emptyLargeTiles_tilesNotSet() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -149,14 +156,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
setLargeTilesSpecsInSharedPreferences(emptySet())
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEmpty()
}
}
@Test
- fun setInitialTilesFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
+ fun setUpgradePathFromSettings_nonCurrentUser_tilesSetForCorrectUser() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -164,7 +174,10 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
fakeUserRepository.setUserInfos(USERS)
fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- underTest.setInitialLargeTilesSpecs(setOf("tileA").toTileSpecs(), ANOTHER_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(setOf("tileA").toTileSpecs()),
+ ANOTHER_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
@@ -174,7 +187,7 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
}
@Test
- fun setInitialTiles_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_noSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -186,14 +199,17 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(currentLargeTiles).isNotEmpty()
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(tiles.toTileSpecs())
}
}
@Test
- fun setInitialTiles_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
+ fun setUpgradePath_afterDefaultRead_largeTilesSetOnRepository_initialTilesCorrect() =
with(kosmos) {
testScope.runTest {
val largeTiles by collectLastValue(underTest.largeTilesSpecs)
@@ -204,15 +220,80 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
assertThat(currentLargeTiles).isNotEmpty()
- underTest.setLargeTilesSpecs(setOf(TileSpec.create("tileC")))
+ underTest.writeLargeTileSpecs(setOf(TileSpec.create("tileC")))
val tiles = setOf("tileA", "tileB")
- underTest.setInitialLargeTilesSpecs(tiles.toTileSpecs(), PRIMARY_USER_ID)
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.ReadFromSettings(tiles.toTileSpecs()),
+ PRIMARY_USER_ID,
+ )
assertThat(largeTiles).isEqualTo(setOf(TileSpec.create("tileC")))
}
}
+ @Test
+ fun setTilesRestored_noLargeTiles_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ assertThat(getSharedPreferences().contains(LARGE_TILES_SPECS_KEY)).isFalse()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
+ @Test
+ fun setDefaultTilesInitial_defaultSetLarge() =
+ with(kosmos) {
+ testScope.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+ }
+
+ @Test
+ fun setTilesRestored_afterDefaultSet_tilesSet() =
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.DefaultSet,
+ PRIMARY_USER_ID,
+ )
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ fakeUserRepository.setUserInfos(USERS)
+ fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ val tiles = setOf("tileA", "tileB").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTiles(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ PRIMARY_USER_ID,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+ }
+
private fun getSharedPreferences(): SharedPreferences =
with(kosmos) {
return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
new file mode 100644
index 000000000000..f3c1f0c9dba8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/LargeTilesUpgradePathsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.mainResources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.shared.model.PackageChangeModel.Empty.packageName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
+import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
+import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
+import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LargeTilesUpgradePathsTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply { defaultTilesRepository = DefaultTilesQSHostRepository(mainResources) }
+
+ private val defaultTiles = kosmos.defaultTilesRepository.defaultTiles.toSet()
+
+ private val underTest = kosmos.qsPreferencesInteractor
+
+ private val Kosmos.userId
+ get() = userRepository.getSelectedUserInfo().id
+
+ private val Kosmos.intent
+ get() =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, kosmos.userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
+
+ /**
+ * This test corresponds to the case of a fresh start.
+ *
+ * The resulting large tiles are the default set of large tiles.
+ */
+ @Test
+ fun defaultTiles_noDataInSharedPreferences_defaultLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded in place from a build that didn't support large
+ * tiles to one that does. The current tiles of the user are read from settings.
+ *
+ * The resulting large tiles are those that were read from Settings.
+ */
+ @Test
+ fun upgradeInPlace_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, and then the user restarts the device, without ever
+ * having modified the set of large tiles.
+ *
+ * The resulting large tiles are the default large tiles that were set on the fresh start
+ */
+ @Test
+ fun defaultSet_restartDevice_largeTilesDontChange() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ // User restarts the device, this will send a read from settings with the default
+ // set of tiles
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(defaultLargeTilesRepository.defaultLargeTiles)
+ }
+
+ /**
+ * This test corresponds to a fresh start, following the user changing the sizes of some tiles.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun defaultSet_someSizeChanges_restart_correctSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ underTest.setLargeTilesSpecs(largeTiles!! + setOf("a", "b").toTileSpecs())
+ val largeTilesBeforeRestart = largeTiles!!
+
+ // Restart
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(defaultTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEqualTo(largeTilesBeforeRestart)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded, and after that performed some size changes.
+ * After that, the user restarts the device.
+ *
+ * The resulting set of large tiles are those that the user determined before restarting the
+ * device.
+ */
+ @Test
+ fun readFromSettings_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. Note that there's no file in SharedPreferences to
+ * restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. However, the restore happens after SystemUI's
+ * initialization has set the tiles to default. Note that there's no file in SharedPreferences
+ * to restore.
+ *
+ * The resulting set of large tiles are those that were restored from the backup.
+ */
+ @Test
+ fun restoreFromBackup_afterDefault_noDataInSharedPreferences_allLargeTiles() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ underTest.setInitialOrUpgradeLargeTilesSpecs(TilesUpgradePath.DefaultSet, userId)
+
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tiles)
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. First the list of tiles is restored in Settings and then a file containing some large
+ * tiles overrides the current shared preferences file
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_thenRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that restored from a build that supported different sizes
+ * tiles. However, this restore of settings happened after SystemUI's restore of the SharedPrefs
+ * containing the user's previous selections to large/small tiles.
+ *
+ * The resulting set of large tiles are those that were restored from the shared preferences
+ * backup (and not the full list).
+ */
+ @Test
+ fun restoreFromBackup_afterRestoreOfSharedPrefs_sharedPrefsAreLarge() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val tiles = setOf("a", "b", "c").toTileSpecs()
+ val tilesFromBackupOfSharedPrefs = setOf("a")
+
+ setLargeTilesSpecsInSharedPreferences(tilesFromBackupOfSharedPrefs)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(tiles),
+ userId,
+ )
+
+ assertThat(largeTiles).isEqualTo(tilesFromBackupOfSharedPrefs.toTileSpecs())
+ }
+
+ /**
+ * This test corresponds to a user that upgraded from a build that didn't support tile sizes to
+ * one that does, via restore from backup. After that, the user modifies the size of some tiles
+ * and then restarts the device.
+ *
+ * The resulting set of large tiles are those after the user modifications.
+ */
+ @Test
+ fun restoreFromBackup_changeSizes_restart_newLargeSet() =
+ kosmos.runTest {
+ val largeTiles by collectLastValue(underTest.largeTilesSpecs)
+ val readTiles = setOf("a", "b", "c").toTileSpecs()
+
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.RestoreFromBackup(readTiles),
+ userId,
+ )
+ underTest.setLargeTilesSpecs(emptySet())
+
+ assertThat(largeTiles).isEmpty()
+
+ // Restart
+ underTest.setInitialOrUpgradeLargeTilesSpecs(
+ TilesUpgradePath.ReadFromSettings(readTiles),
+ userId,
+ )
+ assertThat(largeTiles).isEmpty()
+ }
+
+ private companion object {
+ private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+
+ private fun Kosmos.getSharedPreferences(): SharedPreferences =
+ userFileManager.getSharedPreferences(
+ QSPreferencesRepository.FILE_NAME,
+ Context.MODE_PRIVATE,
+ userRepository.getSelectedUserInfo().id,
+ )
+
+ private fun Kosmos.setLargeTilesSpecsInSharedPreferences(specs: Set<String>) {
+ getSharedPreferences().edit().putStringSet(LARGE_TILES_SPECS_KEY, specs).apply()
+ }
+
+ private fun Kosmos.getLargeTilesSpecsFromSharedPreferences(): Set<String> {
+ return getSharedPreferences().getStringSet(LARGE_TILES_SPECS_KEY, emptySet())!!
+ }
+
+ private fun Set<String>.toTileSpecs(): Set<TileSpec> {
+ return map { TileSpec.create(it) }.toSet()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index 79acfdaa415b..9838bcb86684 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -66,7 +66,7 @@ class IconTilesInteractorTest : SysuiTestCase() {
runCurrent()
// Resize it to large
- qsPreferencesRepository.setLargeTilesSpecs(setOf(spec))
+ qsPreferencesRepository.writeLargeTileSpecs(setOf(spec))
runCurrent()
// Assert that the new tile was added to the large tiles set
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 4b8cd3742bff..d9b3926fa215 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
@@ -242,9 +243,12 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
storeTilesForUser(startingTiles, userId)
val tiles by collectLastValue(underTest.tilesSpecs(userId))
- val tilesRead by collectLastValue(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(startingTiles.toTileSpecs().toSet() to userId)
+ assertThat(tilesRead)
+ .isEqualTo(
+ TilesUpgradePath.ReadFromSettings(startingTiles.toTileSpecs().toSet()) to userId
+ )
}
@Test
@@ -258,13 +262,13 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
val tiles10 by collectLastValue(underTest.tilesSpecs(10))
val tiles11 by collectLastValue(underTest.tilesSpecs(11))
- val tilesRead by collectValues(underTest.tilesReadFromSetting.consumeAsFlow())
+ val tilesRead by collectValues(underTest.tilesUpgradePath.consumeAsFlow())
assertThat(tilesRead).hasSize(2)
assertThat(tilesRead)
.containsExactly(
- startingTiles10.toTileSpecs().toSet() to 10,
- startingTiles11.toTileSpecs().toSet() to 11,
+ TilesUpgradePath.ReadFromSettings(startingTiles10.toTileSpecs().toSet()) to 10,
+ TilesUpgradePath.ReadFromSettings(startingTiles11.toTileSpecs().toSet()) to 11,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 1945f750efaf..29bd18d3f3a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -7,8 +7,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.data.model.RestoreData
-import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepositoryTest.Companion.toTilesSet
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -352,11 +352,11 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
@Test
fun noSettingsStored_noTilesReadFromSettings() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
assertThat(tiles).isEqualTo(getDefaultTileSpecs())
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -365,19 +365,20 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
}
@Test
fun noSettingsStored_tilesChanged_tilesReadFromSettingsNotChanged() =
testScope.runTest {
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
val tiles by collectLastValue(underTest.tiles())
underTest.addTile(TileSpec.create("a"))
- assertThat(tilesRead).isEqualTo(null)
+ assertThat(tilesRead).isEqualTo(TilesUpgradePath.DefaultSet)
}
@Test
@@ -386,10 +387,34 @@ class UserTileSpecRepositoryTest : SysuiTestCase() {
val storedTiles = "a,b"
storeTiles(storedTiles)
val tiles by collectLastValue(underTest.tiles())
- val tilesRead by collectLastValue(underTest.tilesReadFromSettings.consumeAsFlow())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
underTest.addTile(TileSpec.create("c"))
- assertThat(tilesRead).isEqualTo(storedTiles.toTilesSet())
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.ReadFromSettings(storedTiles.toTilesSet()))
+ }
+
+ @Test
+ fun tilesRestoredFromBackup() =
+ testScope.runTest {
+ val specsBeforeRestore = "a,b,c,d,e"
+ val restoredSpecs = "a,c,d,f"
+ val autoAddedBeforeRestore = "b,d"
+ val restoredAutoAdded = "d,e"
+
+ storeTiles(specsBeforeRestore)
+ val tiles by collectLastValue(underTest.tiles())
+ val tilesRead by collectLastValue(underTest.tilesUpgradePath.consumeAsFlow())
+ runCurrent()
+
+ val restoreData =
+ RestoreData(restoredSpecs.toTileSpecs(), restoredAutoAdded.toTilesSet(), USER)
+ underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet())
+ runCurrent()
+
+ val expected = "a,b,c,d,f"
+ assertThat(tilesRead)
+ .isEqualTo(TilesUpgradePath.RestoreFromBackup(expected.toTilesSet()))
}
private fun getDefaultTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 403ac3288128..20637cd4af33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,7 +293,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
- fun chipsLegacy_twoTimerChips_isSmallPortrait_andChipsModernizationDisabled_bothSquished() =
+ fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
@@ -307,6 +307,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoTimerChips_isSmallPortrait_bothSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // Squished chips are icon only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
@@ -324,6 +340,23 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000)
+ addOngoingCallState(key = "call")
+
+ val latest by collectLastValue(underTest.chips)
+
+ // The screen record countdown isn't squished to icon-only
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Countdown::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
@@ -360,6 +393,38 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Inactive::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_numberOfChipsChanges_chipsGetSquishedAndUnsquished() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ // WHEN there's only one chip
+ screenRecordState.value = ScreenRecordModel.Recording
+ removeOngoingCallState(key = "call")
+
+ // The screen record isn't squished because it's the only one
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+
+ // WHEN there's 2 chips
+ addOngoingCallState(key = "call")
+
+ // THEN they both become squished
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+ // But the call chip *is* squished
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java)
+
+ // WHEN we go back down to 1 chip
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ // THEN the remaining chip unsquishes
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLandscape_notSquished() =
@@ -383,6 +448,29 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
+ @EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ @Test
+ fun chips_twoChips_isLandscape_notSquished() =
+ kosmos.runTest {
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(key = "call")
+
+ // WHEN we're in landscape
+ val config =
+ Configuration(kosmos.mainResources.configuration).apply {
+ orientation = Configuration.ORIENTATION_LANDSCAPE
+ }
+ kosmos.fakeConfigurationRepository.onConfigurationChange(config)
+
+ val latest by collectLastValue(underTest.chips)
+
+ // THEN the chips aren't squished (squished chips would be icon only)
+ assertThat(latest!!.active[0])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat(latest!!.active[1])
+ .isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ }
+
@DisableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
@Test
fun chipsLegacy_twoChips_isLargeScreen_notSquished() =
@@ -402,16 +490,19 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
}
- @Test
@EnableFlags(StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun chips_twoChips_chipsModernizationEnabled_notSquished() =
+ @Test
+ fun chips_twoChips_isLargeScreen_notSquished() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
addOngoingCallState(key = "call")
+ // WHEN we're on a large screen
+ kosmos.displayStateRepository.setIsLargeScreen(true)
+
val latest by collectLastValue(underTest.chips)
- // Squished chips would be icon only
+ // THEN the chips aren't squished (squished chips would be icon only)
assertThat(latest!!.active[0])
.isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
assertThat(latest!!.active[1])
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index fecf1fd2f222..354edac75452 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -20,9 +20,12 @@ import android.content.Context
import android.content.res.Resources
import android.hardware.devicestate.DeviceStateManager
import android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD
+import android.os.PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
@@ -44,8 +47,10 @@ import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.COOL_DOWN_DURATION
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.SCREEN_EVENT_TIMEOUT
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -56,11 +61,13 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -73,6 +80,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -88,6 +96,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
private val animationStatusRepository = kosmos.fakeAnimationStatusRepository
private val keyguardInteractor = mock<KeyguardInteractor>()
private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
+ private val latencyTracker = mock<LatencyTracker>()
private val deviceStateManager = kosmos.deviceStateManager
private val closedDeviceState = kosmos.foldedDeviceStateList.first()
@@ -142,6 +151,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
displaySwitchLatencyLogger,
systemClock,
deviceStateManager,
+ latencyTracker,
)
}
@@ -195,6 +205,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
displaySwitchLatencyLogger,
systemClock,
deviceStateManager,
+ latencyTracker,
)
displaySwitchLatencyTracker.start()
@@ -370,6 +381,256 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
}
}
+ @Test
+ fun unfoldingDevice_startsLatencyTracking() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+
+ verify(latencyTracker).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun foldingDevice_doesntTrackLatency() {
+ testScope.runTest {
+ setDeviceState(UNFOLDED)
+ displaySwitchLatencyTracker.start()
+ runCurrent()
+
+ startFolding()
+
+ verify(latencyTracker, never()).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun foldedState_doesntStartTrackingOnScreenOn() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verify(latencyTracker, never()).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun unfoldingDevice_endsLatencyTrackingWhenTransitionStarts() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun unfoldingDevice_animationsDisabled_endsLatencyTrackingWhenScreenOn() {
+ testScope.runTest {
+ animationStatusRepository.onAnimationStatusChanged(enabled = false)
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun unfoldingDevice_doesntEndLatencyTrackingWhenScreenOn() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun unfoldingDevice_animationsDisabled_endsLatencyTrackingWhenDeviceGoesToSleep() {
+ testScope.runTest {
+ animationStatusRepository.onAnimationStatusChanged(enabled = false)
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_POWER_BUTTON)
+ runCurrent()
+
+ verify(latencyTracker).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchInterrupted_cancelsTrackingWhenNewDeviceStateEmitted() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ finishFolding()
+
+ verify(latencyTracker).onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD)
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchInterrupted_cancelsTrackingForManyStateChanges() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ startUnfolding()
+ startFolding()
+ startUnfolding()
+ finishUnfolding()
+
+ verify(latencyTracker).onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD)
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchInterrupted_startsOneTrackingForManyStateChanges() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ startUnfolding()
+ startFolding()
+ startUnfolding()
+
+ verify(latencyTracker, times(1)).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun interruptedDisplaySwitchFinished_inCoolDownPeriod_trackingDisabled() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ finishFolding()
+
+ advanceTimeBy(COOL_DOWN_DURATION.minus(10.milliseconds))
+ startUnfolding()
+ finishUnfolding()
+
+ verify(latencyTracker, times(1)).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun interruptedDisplaySwitchFinished_coolDownPassed_trackingWorksAsUsual() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ finishFolding()
+
+ advanceTimeBy(COOL_DOWN_DURATION.plus(10.milliseconds))
+ startUnfolding()
+ finishUnfolding()
+
+ verify(latencyTracker, times(2)).onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ verify(latencyTracker).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchInterrupted_coolDownExtendedByStartEvents() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ advanceTimeBy(COOL_DOWN_DURATION.minus(10.milliseconds))
+ startUnfolding()
+ advanceTimeBy(20.milliseconds)
+
+ startFolding()
+ finishUnfolding()
+
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchInterrupted_coolDownExtendedByAnyEndEvent() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ startFolding()
+ startUnfolding()
+ advanceTimeBy(COOL_DOWN_DURATION - 10.milliseconds)
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ advanceTimeBy(20.milliseconds)
+
+ startFolding()
+ finishUnfolding()
+
+ verify(latencyTracker, never()).onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ @Test
+ fun displaySwitchTimedOut_trackingCancelled() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+
+ startUnfolding()
+ advanceTimeBy(SCREEN_EVENT_TIMEOUT + 10.milliseconds)
+ finishUnfolding()
+
+ verify(latencyTracker).onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ }
+
+ private suspend fun TestScope.startInFoldedState(tracker: DisplaySwitchLatencyTracker) {
+ setDeviceState(FOLDED)
+ tracker.start()
+ runCurrent()
+ }
+
+ private suspend fun TestScope.startUnfolding() {
+ setDeviceState(HALF_FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
+ runCurrent()
+ }
+
+ private suspend fun TestScope.startFolding() {
+ setDeviceState(FOLDED)
+ powerInteractor.setScreenPowerState(SCREEN_OFF)
+ runCurrent()
+ }
+
+ private fun TestScope.finishFolding() {
+ powerInteractor.setScreenPowerState(SCREEN_ON)
+ runCurrent()
+ }
+
+ private fun TestScope.finishUnfolding() {
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ runCurrent()
+ }
+
private suspend fun setDeviceState(state: DeviceState) {
foldStateRepository.emit(state)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 75f3386ed695..b8e19248b2de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -47,6 +47,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.TestScopeProvider;
+import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -268,13 +269,15 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
@Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(sessionId, 0);
}
@Test
public void testOnRemoteRemove_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
- mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+ var sessionId = MediaSessions.SessionId.Companion.from(token);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(sessionId);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
index 61ee5e04afd9..390518f3e2e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.window.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -32,6 +34,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
class WindowRootViewModelTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
new file mode 100644
index 000000000000..1ba637f379c1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background_inset.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/clipboard_minimized_background"
+ android:inset="4dp"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 448b3e7d5ea0..915563b1ae20 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -171,12 +171,12 @@
android:layout_height="wrap_content"
android:visibility="gone"
android:elevation="7dp"
- android:padding="8dp"
+ android:padding="12dp"
app:layout_constraintBottom_toTopOf="@id/indication_container"
app:layout_constraintStart_toStartOf="parent"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
- android:background="@drawable/clipboard_minimized_background">
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="2dp"
+ android:background="@drawable/clipboard_minimized_background_inset">
<ImageView
android:src="@drawable/ic_content_paste"
android:tint="?attr/overlayButtonTextColor"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 67f620f6fc54..8ad99abccdfe 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,7 +16,7 @@
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/volume_dialog_root"
+ android:id="@+id/volume_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
index 6748cfa05c35..4e3c8cc4413b 100644
--- a/packages/SystemUI/res/layout/volume_ringer_button.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -13,20 +13,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" >
-
- <ImageButton
- android:id="@+id/volume_drawer_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
- android:contentDescription="@string/volume_ringer_mode"
- android:gravity="center"
- android:tint="@androidprv:color/materialColorOnSurface"
- android:src="@drawable/volume_ringer_item_bg"
- android:background="@drawable/volume_ringer_item_bg"/>
-
-</FrameLayout>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_ringer_item_bg"
+ android:contentDescription="@string/volume_ringer_mode"
+ android:gravity="center"
+ android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+ android:src="@drawable/volume_ringer_item_bg"
+ android:tint="@androidprv:color/materialColorOnSurface" />
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS
index 1ed8c068f974..5a59b7aaef56 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
include /core/java/android/view/accessibility/OWNERS
jonesriley@google.com \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
index b65d29c6a0bb..429b4b0fccab 100644
--- a/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
@@ -5,5 +5,4 @@ linyuh@google.com
pauldpong@google.com
praveenj@google.com
vicliang@google.com
-mfolkerts@google.com
yuklimko@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
index 2e1b5ad177b5..e456310febfd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java
@@ -17,16 +17,19 @@
package com.android.systemui.communal;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.DozeStateModel;
import com.android.systemui.shared.condition.Condition;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
import javax.inject.Inject;
@@ -38,6 +41,10 @@ public class DeviceInactiveCondition extends Condition {
private final KeyguardStateController mKeyguardStateController;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardInteractor mKeyguardInteractor;
+ private final JavaAdapter mJavaAdapter;
+ private Job mAnyDozeListenerJob;
+ private boolean mAnyDoze;
private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
@@ -63,12 +70,14 @@ public class DeviceInactiveCondition extends Condition {
@Inject
public DeviceInactiveCondition(@Application CoroutineScope scope,
KeyguardStateController keyguardStateController,
- WakefulnessLifecycle wakefulnessLifecycle,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardInteractor keyguardInteractor, JavaAdapter javaAdapter) {
super(scope);
mKeyguardStateController = keyguardStateController;
mWakefulnessLifecycle = wakefulnessLifecycle;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardInteractor = keyguardInteractor;
+ mJavaAdapter = javaAdapter;
}
@Override
@@ -77,6 +86,11 @@ public class DeviceInactiveCondition extends Condition {
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ mAnyDozeListenerJob = mJavaAdapter.alwaysCollectFlow(
+ mKeyguardInteractor.getDozeTransitionModel(), dozeModel -> {
+ mAnyDoze = !DozeStateModel.Companion.isDozeOff(dozeModel.getTo());
+ updateState();
+ });
}
@Override
@@ -84,6 +98,7 @@ public class DeviceInactiveCondition extends Condition {
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ mAnyDozeListenerJob.cancel(null);
}
@Override
@@ -92,10 +107,10 @@ public class DeviceInactiveCondition extends Condition {
}
private void updateState() {
- final boolean asleep =
- mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
- || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP;
- updateCondition(asleep || mKeyguardStateController.isShowing()
- || mKeyguardUpdateMonitor.isDreaming());
+ final boolean asleep = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP;
+ // Doze/AoD is also a dream, but we should never override it with low light as to the user
+ // it's totally unrelated.
+ updateCondition(!mAnyDoze && (asleep || mKeyguardStateController.isShowing()
+ || mKeyguardUpdateMonitor.isDreaming()));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index fcc3ea9f7d58..fed77090c477 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -18,6 +18,7 @@ package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.unfoldLatencyTrackingFix
import com.android.systemui.LatencyTester
import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.Magnification
@@ -60,6 +61,7 @@ import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
import com.android.systemui.unfold.DisplaySwitchLatencyTracker
+import com.android.systemui.unfold.NoCooldownDisplaySwitchLatencyTracker
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
@@ -67,8 +69,10 @@ import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import javax.inject.Provider
/**
* DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
@@ -148,12 +152,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(LatencyTester::class)
abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
- /** Inject into DisplaySwitchLatencyTracker. */
- @Binds
- @IntoMap
- @ClassKey(DisplaySwitchLatencyTracker::class)
- abstract fun bindDisplaySwitchLatencyTracker(sysui: DisplaySwitchLatencyTracker): CoreStartable
-
/** Inject into NotificationChannels. */
@Binds
@IntoMap
@@ -353,4 +351,15 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(ComplicationTypesUpdater::class)
abstract fun bindComplicationTypesUpdater(updater: ComplicationTypesUpdater): CoreStartable
+
+ companion object {
+ @Provides
+ @IntoMap
+ @ClassKey(DisplaySwitchLatencyTracker::class)
+ fun provideDisplaySwitchLatencyTracker(
+ noCoolDownVariant: Provider<NoCooldownDisplaySwitchLatencyTracker>,
+ coolDownVariant: Provider<DisplaySwitchLatencyTracker>,
+ ): CoreStartable =
+ if (unfoldLatencyTrackingFix()) coolDownVariant.get() else noCoolDownVariant.get()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f85a23c1f091..eb96c921c181 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,6 +24,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -57,6 +58,7 @@ constructor(
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryInteractor: DeviceEntryInteractor,
@@ -116,6 +118,17 @@ constructor(
}
}
+ @SuppressLint("MissingPermission")
+ private fun shouldTransitionToCommunal(
+ shouldShowCommunal: Boolean,
+ isCommunalAvailable: Boolean,
+ ) =
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ shouldShowCommunal
+ } else {
+ isCommunalAvailable && dreamManager.canStartDreaming(false)
+ }
+
@OptIn(FlowPreview::class)
@SuppressLint("MissingPermission")
private fun listenForDozingToDreaming() {
@@ -141,9 +154,10 @@ constructor(
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
communalInteractor.isCommunalAvailable,
+ communalInteractor.shouldShowCommunal,
communalSceneInteractor.isIdleOnCommunal,
)
- .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+ .collect { (_, isCommunalAvailable, shouldShowCommunal, isIdleOnCommunal) ->
val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
val isKeyguardGoingAway = keyguardInteractor.isKeyguardGoingAway.value
@@ -177,11 +191,9 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // Using false for isScreenOn as canStartDreaming returns false if any
- // dream, including doze, is active.
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
@@ -203,6 +215,7 @@ constructor(
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
+ communalInteractor.shouldShowCommunal,
communalInteractor.isCommunalAvailable,
communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
@@ -212,6 +225,7 @@ constructor(
.collect {
(
_,
+ shouldShowCommunal,
isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
@@ -245,7 +259,9 @@ constructor(
ownerReason = "waking from dozing",
)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ } else if (
+ shouldTransitionToCommunal(shouldShowCommunal, isCommunalAvailable)
+ ) {
if (!SceneContainerFlag.isEnabled) {
transitionToGlanceableHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 251af11f7fe6..c1c509b8fd57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -129,20 +129,37 @@ constructor(
if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
if (SceneContainerFlag.isEnabled) return
scope.launch {
- powerInteractor.isAwake
- .debounce(50L)
- .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
- .sample(communalInteractor.isCommunalAvailable)
- .collect { isCommunalAvailable ->
- if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
- // This case handles tapping the power button to transition through
- // dream -> off -> hub.
- communalSceneInteractor.snapToScene(
- newScene = CommunalScenes.Communal,
- loggingReason = "from dreaming to hub",
- )
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.shouldShowCommunal)
+ .collect { shouldShowCommunal ->
+ if (shouldShowCommunal) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
}
- }
+ } else {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.isCommunalAvailable)
+ .collect { isCommunalAvailable ->
+ if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
index 8469cb4ab565..f8072f2f79b4 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
@@ -78,7 +78,7 @@ public abstract class LowLightModule {
@Provides
@IntoSet
- @Named(com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS)
+ @Named(LOW_LIGHT_PRECONDITIONS)
static Condition provideLowLightCondition(LowLightCondition lowLightCondition,
DirectBootCondition directBootCondition) {
// Start lowlight if we are either in lowlight or in direct boot. The ordering of the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 16dff7d11002..11b014c2147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -28,6 +28,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
@@ -83,34 +84,78 @@ constructor(
.flowOn(backgroundDispatcher)
/** Sets for the current user the set of [TileSpec] to display as large tiles. */
- fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- setLargeTilesSpecsForUser(specs, userRepository.getSelectedUserInfo().id)
+ fun writeLargeTileSpecs(specs: Set<TileSpec>) {
+ with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
+ writeLargeTileSpecs(specs)
+ setLargeTilesDefault(false)
+ }
}
- private fun setLargeTilesSpecsForUser(specs: Set<TileSpec>, userId: Int) {
- with(getSharedPrefs(userId)) {
- edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ suspend fun deleteLargeTileDataJob() {
+ userRepository.selectedUserInfo.collect { userInfo ->
+ getSharedPrefs(userInfo.id)
+ .edit()
+ .remove(LARGE_TILES_SPECS_KEY)
+ .remove(LARGE_TILES_DEFAULT_KEY)
+ .apply()
}
}
+ private fun SharedPreferences.writeLargeTileSpecs(specs: Set<TileSpec>) {
+ edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
+ }
+
/**
- * Sets the initial tiles as large, if there is no set in SharedPrefs for the [userId]. This is
- * to be used when upgrading to a build that supports large/small tiles.
+ * Sets the initial set of large tiles. One of the following cases will happen:
+ * * If we are setting the default set (no value stored in settings for the list of tiles), set
+ * the large tiles based on [defaultLargeTilesRepository]. We do this to signal future reboots
+ * that we have performed the upgrade path once. In this case, we will mark that we set them
+ * as the default in case a restore needs to modify them later.
+ * * If we got a list of tiles restored from a device and nothing has modified the list of
+ * tiles, set all the restored tiles to large. Note that if we also restored a set of large
+ * tiles before this was called, [LARGE_TILES_DEFAULT_KEY] will be false and we won't
+ * overwrite it.
+ * * If we got a list of tiles from settings, we consider that we upgraded in place and then we
+ * will set all those tiles to large IF there's no current set of large tiles.
*
* Even if largeTilesSpec is read Eagerly before we know if we are in an initial state, because
* we are not writing the default values to the SharedPreferences, the file will not contain the
* key and this call will succeed, as long as there hasn't been any calls to setLargeTilesSpecs
* for that user before.
*/
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, userId: Int) {
+ fun setInitialOrUpgradeLargeTiles(upgradePath: TilesUpgradePath, userId: Int) {
with(getSharedPrefs(userId)) {
- if (!contains(LARGE_TILES_SPECS_KEY)) {
- logger.i("Setting upgraded large tiles for user $userId: $specs")
- setLargeTilesSpecsForUser(specs, userId)
+ when (upgradePath) {
+ is TilesUpgradePath.DefaultSet -> {
+ writeLargeTileSpecs(defaultLargeTilesRepository.defaultLargeTiles)
+ logger.i("Large tiles set to default on init")
+ setLargeTilesDefault(true)
+ }
+ is TilesUpgradePath.RestoreFromBackup -> {
+ if (
+ getBoolean(LARGE_TILES_DEFAULT_KEY, false) ||
+ !contains(LARGE_TILES_SPECS_KEY)
+ ) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles restored from backup set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
+ is TilesUpgradePath.ReadFromSettings -> {
+ if (!contains(LARGE_TILES_SPECS_KEY)) {
+ writeLargeTileSpecs(upgradePath.value)
+ logger.i("Tiles read from settings set to large: ${upgradePath.value}")
+ setLargeTilesDefault(false)
+ }
+ }
}
}
}
+ private fun SharedPreferences.setLargeTilesDefault(value: Boolean) {
+ edit().putBoolean(LARGE_TILES_DEFAULT_KEY, value).apply()
+ }
+
private fun getSharedPrefs(userId: Int): SharedPreferences {
return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
}
@@ -118,6 +163,7 @@ constructor(
companion object {
private const val TAG = "QSPreferencesRepository"
private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
+ private const val LARGE_TILES_DEFAULT_KEY = "large_tiles_default"
const val FILE_NAME = "quick_settings_prefs"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
index 86838b438bc6..9b98797ef393 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,10 +28,20 @@ class QSPreferencesInteractor @Inject constructor(private val repo: QSPreference
val largeTilesSpecs: Flow<Set<TileSpec>> = repo.largeTilesSpecs
fun setLargeTilesSpecs(specs: Set<TileSpec>) {
- repo.setLargeTilesSpecs(specs)
+ repo.writeLargeTileSpecs(specs)
}
- fun setInitialLargeTilesSpecs(specs: Set<TileSpec>, user: Int) {
- repo.setInitialLargeTilesSpecs(specs, user)
+ /**
+ * This method should be called to indicate that a "new" set of tiles has been determined for a
+ * particular user coming from different upgrade sources.
+ *
+ * @see TilesUpgradePath for more information
+ */
+ fun setInitialOrUpgradeLargeTilesSpecs(specs: TilesUpgradePath, user: Int) {
+ repo.setInitialOrUpgradeLargeTiles(specs, user)
+ }
+
+ suspend fun deleteLargeTilesDataJob() {
+ repo.deleteLargeTileDataJob()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
index a8ac5c34d8f9..e2797356fa96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/startable/QSPanelsCoreStartable.kt
@@ -19,11 +19,13 @@ package com.android.systemui.qs.panels.domain.startable
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.panels.domain.interactor.QSPreferencesInteractor
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
class QSPanelsCoreStartable
@Inject
@@ -33,10 +35,14 @@ constructor(
@Background private val backgroundApplicationScope: CoroutineScope,
) : CoreStartable {
override fun start() {
- backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
- tileSpecRepository.tilesReadFromSetting.receiveAsFlow().collect { (tiles, userId) ->
- preferenceInteractor.setInitialLargeTilesSpecs(tiles, userId)
+ if (QsInCompose.isEnabled) {
+ backgroundApplicationScope.launchTraced("QSPanelsCoreStartable.startingLargeTiles") {
+ tileSpecRepository.tilesUpgradePath.receiveAsFlow().collect { (tiles, userId) ->
+ preferenceInteractor.setInitialOrUpgradeLargeTilesSpecs(tiles, userId)
+ }
}
+ } else {
+ backgroundApplicationScope.launch { preferenceInteractor.deleteLargeTilesDataJob() }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 6b7dd386bb46..c50d5dad10c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -24,6 +24,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.RetailModeRepository
@@ -78,7 +79,7 @@ interface TileSpecRepository {
/** Reset the current set of tiles to the default list of tiles */
suspend fun resetToDefault(userId: Int)
- val tilesReadFromSetting: ReceiveChannel<Pair<Set<TileSpec>, Int>>
+ val tilesUpgradePath: ReceiveChannel<Pair<TilesUpgradePath, Int>>
companion object {
/** Position to indicate the end of the list */
@@ -112,8 +113,8 @@ constructor(
.filter { it !is TileSpec.Invalid }
}
- private val _tilesReadFromSetting = Channel<Pair<Set<TileSpec>, Int>>(capacity = 5)
- override val tilesReadFromSetting = _tilesReadFromSetting
+ private val _tilesUpgradePath = Channel<Pair<TilesUpgradePath, Int>>(capacity = 5)
+ override val tilesUpgradePath = _tilesUpgradePath
private val userTileRepositories = SparseArray<UserTileSpecRepository>()
@@ -122,8 +123,8 @@ constructor(
val userTileRepository = userTileSpecRepositoryFactory.create(userId)
userTileRepositories.put(userId, userTileRepository)
applicationScope.launchTraced("TileSpecRepository.aggregateTilesPerUser") {
- for (tilesFromSettings in userTileRepository.tilesReadFromSettings) {
- _tilesReadFromSetting.send(tilesFromSettings to userId)
+ for (tileUpgrade in userTileRepository.tilesUpgradePath) {
+ _tilesUpgradePath.send(tileUpgrade to userId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 7b56cd92a081..5aa5edaa726e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -9,6 +9,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.settings.SecureSettings
import dagger.assisted.Assisted
@@ -49,8 +50,8 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- private val _tilesReadFromSettings = Channel<Set<TileSpec>>(capacity = 2)
- val tilesReadFromSettings: ReceiveChannel<Set<TileSpec>> = _tilesReadFromSettings
+ private val _tilesUpgradePath = Channel<TilesUpgradePath>(capacity = 3)
+ val tilesUpgradePath: ReceiveChannel<TilesUpgradePath> = _tilesUpgradePath
private val defaultTiles: List<TileSpec>
get() = defaultTilesRepository.defaultTiles
@@ -67,14 +68,23 @@ constructor(
.scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
change
.apply(current)
- .also {
- if (current != it) {
+ .also { afterRestore ->
+ if (current != afterRestore) {
if (change is RestoreTiles) {
- logger.logTilesRestoredAndReconciled(current, it, userId)
+ logger.logTilesRestoredAndReconciled(
+ current,
+ afterRestore,
+ userId,
+ )
} else {
- logger.logProcessTileChange(change, it, userId)
+ logger.logProcessTileChange(change, afterRestore, userId)
}
}
+ if (change is RestoreTiles) {
+ _tilesUpgradePath.send(
+ TilesUpgradePath.RestoreFromBackup(afterRestore.toSet())
+ )
+ }
}
// Distinct preserves the order of the elements removing later
// duplicates,
@@ -154,7 +164,9 @@ constructor(
private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> {
val loadedTiles = loadTilesFromSettings(userId)
if (loadedTiles.isNotEmpty()) {
- _tilesReadFromSettings.send(loadedTiles.toSet())
+ _tilesUpgradePath.send(TilesUpgradePath.ReadFromSettings(loadedTiles.toSet()))
+ } else {
+ _tilesUpgradePath.send(TilesUpgradePath.DefaultSet)
}
return parseTileSpecs(loadedTiles, userId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
new file mode 100644
index 000000000000..98f30c22d0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TilesUpgradePath.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.shared
+
+/** Upgrade paths indicating the source of the list of QS tiles. */
+sealed interface TilesUpgradePath {
+
+ sealed interface UpgradeWithTiles : TilesUpgradePath {
+ val value: Set<TileSpec>
+ }
+
+ /** This indicates a set of tiles that was read from Settings on user start */
+ @JvmInline value class ReadFromSettings(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /** This indicates a set of tiles that was restored from backup */
+ @JvmInline value class RestoreFromBackup(override val value: Set<TileSpec>) : UpgradeWithTiles
+
+ /**
+ * This indicates that no tiles were read from Settings on user start so the default has been
+ * stored.
+ */
+ data object DefaultSet : TilesUpgradePath
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 3ba0ae3b3cb6..1a30caf0150b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -214,7 +214,6 @@ constructor(
if (
secondaryChip is InternalChipModel.Active &&
StatusBarNotifChips.isEnabled &&
- !StatusBarChipsModernization.isEnabled &&
!isScreenReasonablyLarge
) {
// If we have two showing chips and we don't have a ton of room
@@ -222,8 +221,10 @@ constructor(
// possible so that we have the highest chance of showing both chips (as
// opposed to showing the primary chip with a lot of text and completely
// hiding the secondary chip).
- // Also: If StatusBarChipsModernization is enabled, then we'll do the
- // squishing in Compose instead.
+ // TODO(b/392895330): If StatusBarChipsModernization is enabled, do the
+ // squishing in Compose instead, and be smart about it (e.g. if we have
+ // room for the first chip to show text and the second chip to be icon-only,
+ // do that instead of always squishing both chips.)
InternalMultipleOngoingActivityChipsModel(
primaryChip.squish(),
secondaryChip.squish(),
@@ -237,24 +238,31 @@ constructor(
/** Squishes the chip down to the smallest content possible. */
private fun InternalChipModel.Active.squish(): InternalChipModel.Active {
- return when (model) {
+ return if (model.shouldSquish()) {
+ InternalChipModel.Active(this.type, this.model.toIconOnly())
+ } else {
+ this
+ }
+ }
+
+ private fun OngoingActivityChipModel.Active.shouldSquish(): Boolean {
+ return when (this) {
// Icon-only is already maximum squished
- is OngoingActivityChipModel.Active.IconOnly -> this
+ is OngoingActivityChipModel.Active.IconOnly,
// Countdown shows just a single digit, so already maximum squished
- is OngoingActivityChipModel.Active.Countdown -> this
- // The other chips have icon+text, so we should hide the text
+ is OngoingActivityChipModel.Active.Countdown -> false
+ // The other chips have icon+text, so we can squish them by hiding text
is OngoingActivityChipModel.Active.Timer,
is OngoingActivityChipModel.Active.ShortTimeDelta,
- is OngoingActivityChipModel.Active.Text ->
- InternalChipModel.Active(this.type, this.model.toIconOnly())
+ is OngoingActivityChipModel.Active.Text -> true
}
}
private fun OngoingActivityChipModel.Active.toIconOnly(): OngoingActivityChipModel.Active {
// If this chip doesn't have an icon, then it only has text and we should continue showing
// its text. (This is theoretically impossible because
- // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon, but protect
- // against it just in case.)
+ // [OngoingActivityChipModel.Active.Countdown] is the only chip without an icon and
+ // [shouldSquish] returns false for that model, but protect against it just in case.)
val currentIcon = icon ?: return this
return OngoingActivityChipModel.Active.IconOnly(
key,
@@ -271,8 +279,38 @@ constructor(
*/
val chips: StateFlow<MultipleOngoingActivityChipsModel> =
if (StatusBarChipsModernization.isEnabled) {
- incomingChipBundle
- .map { bundle -> rankChips(bundle) }
+ combine(
+ incomingChipBundle.map { bundle -> rankChips(bundle) },
+ isScreenReasonablyLarge,
+ ) { rankedChips, isScreenReasonablyLarge ->
+ if (
+ StatusBarNotifChips.isEnabled &&
+ !isScreenReasonablyLarge &&
+ rankedChips.active.filter { !it.isHidden }.size >= 2
+ ) {
+ // If we have at least two showing chips and we don't have a ton of room
+ // (!isScreenReasonablyLarge), then we want to make both of them as small as
+ // possible so that we have the highest chance of showing both chips (as
+ // opposed to showing the first chip with a lot of text and completely
+ // hiding the other chips).
+ val squishedActiveChips =
+ rankedChips.active.map {
+ if (!it.isHidden && it.shouldSquish()) {
+ it.toIconOnly()
+ } else {
+ it
+ }
+ }
+
+ MultipleOngoingActivityChipsModel(
+ active = squishedActiveChips,
+ overflow = rankedChips.overflow,
+ inactive = rankedChips.inactive,
+ )
+ } else {
+ rankedChips
+ }
+ }
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel())
} else {
MutableStateFlow(MultipleOngoingActivityChipsModel()).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index e33baf7c33ae..ded964d8a1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -57,11 +57,11 @@ import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.CommandQueue;
@@ -76,11 +76,11 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
@@ -115,7 +115,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final static String TAG = "StatusBarNotificationActivityStarter";
private final Context mContext;
- private final int mDisplayId;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
@@ -155,8 +154,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Inject
StatusBarNotificationActivityStarter(
- Context context,
- @DisplayId int displayId,
+ @ShadeDisplayAware Context context,
Handler mainThreadHandler,
@Background Executor uiBgExecutor,
NotificationVisibilityProvider visibilityProvider,
@@ -189,7 +187,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PowerInteractor powerInteractor,
UserTracker userTracker) {
mContext = context;
- mDisplayId = displayId;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
mVisibilityProvider = visibilityProvider;
@@ -493,6 +490,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean animate,
boolean isActivityIntent) {
mLogger.logStartNotificationIntent(entry);
+ final int displayId = mContext.getDisplayId();
try {
ActivityTransitionAnimator.Controller animationController =
new StatusBarTransitionAnimatorController(
@@ -501,7 +499,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
isActivityIntent);
mActivityTransitionAnimator.startPendingIntentWithAnimation(
animationController,
@@ -511,11 +509,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
long eventTime = row.getAndResetLastActionUpTime();
Bundle options = eventTime > 0
? getActivityOptions(
- mDisplayId,
+ displayId,
adapter,
mKeyguardStateController.isShowing(),
eventTime)
- : getActivityOptions(mDisplayId, adapter);
+ : getActivityOptions(displayId, adapter);
int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
mLogger.logSendPendingIntent(entry, intent, result);
@@ -533,6 +531,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
public void startNotificationGutsIntent(@NonNull final Intent intent, final int appUid,
@NonNull ExpandableNotificationRow row) {
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
+ final int displayId = mContext.getDisplayId();
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -544,7 +543,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
@@ -552,7 +551,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
- mDisplayId,
+ displayId,
adapter),
new UserHandle(UserHandle.getUserId(appUid))));
});
@@ -571,6 +570,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startHistoryIntent(View view, boolean showHistory) {
ModesEmptyShadeFix.assertInLegacyMode();
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -597,13 +597,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intent.getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
@@ -620,6 +620,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
+ final int displayId = mContext.getDisplayId();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -642,13 +643,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mShadeController,
mNotificationShadeWindowController,
mCommandQueue,
- mDisplayId,
+ displayId,
true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intentInfo.getTargetIntent().getPackage(),
(adapter) -> tsb.startActivities(
- getActivityOptions(mDisplayId, adapter),
+ getActivityOptions(displayId, adapter),
mUserTracker.getUserHandle()));
});
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
index 0ec996be72de..9b4902a9e7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
@@ -6,5 +6,4 @@ madym@google.com
mgalhardo@google.com
petrcermak@google.com
stevenckng@google.com
-tkachenkoi@google.com
-vanjan@google.com \ No newline at end of file
+vanjan@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index f5aac720fd47..cd401d5deb6e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -19,8 +19,11 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.app.tracing.TraceUtils.traceAsync
import com.android.app.tracing.instantForTrack
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -30,10 +33,12 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.Compile
import com.android.systemui.util.Utils.isDeviceFoldable
@@ -42,17 +47,23 @@ import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.race
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.measureTimeMillis
-import java.time.Duration
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
/**
@@ -73,63 +84,96 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
private val systemClock: SystemClock,
- private val deviceStateManager: DeviceStateManager
+ private val deviceStateManager: DeviceStateManager,
+ private val latencyTracker: LatencyTracker,
) : CoreStartable {
private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
private val isAodEnabled: Boolean
get() = keyguardInteractor.isAodAvailable.value
+ private val displaySwitchStarted =
+ deviceStateRepository.state.pairwise().filter {
+ // Start tracking only when the foldable device is
+ // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+ foldableDeviceState ->
+ foldableDeviceState.previousValue == DeviceState.FOLDED ||
+ foldableDeviceState.newValue == DeviceState.FOLDED
+ }
+
+ private var startOrEndEvent: Flow<Any> = merge(displaySwitchStarted, anyEndEventFlow())
+
+ private var isCoolingDown = false
+
override fun start() {
if (!isDeviceFoldable(context.resources, deviceStateManager)) {
return
}
applicationScope.launch(context = backgroundDispatcher) {
- deviceStateRepository.state
- .pairwise()
- .filter {
- // Start tracking only when the foldable device is
- // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
- // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
- foldableDeviceState ->
- foldableDeviceState.previousValue == DeviceState.FOLDED ||
- foldableDeviceState.newValue == DeviceState.FOLDED
+ displaySwitchStarted.collectLatest { (previousState, newState) ->
+ if (isCoolingDown) return@collectLatest
+ if (previousState == DeviceState.FOLDED) {
+ latencyTracker.onActionStart(ACTION_SWITCH_DISPLAY_UNFOLD)
+ instantForTrack(TAG) { "unfold latency tracking started" }
}
- .flatMapLatest { foldableDeviceState ->
- flow {
- var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
- val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
- displaySwitchLatencyEvent =
- displaySwitchLatencyEvent.withBeforeFields(
- foldableDeviceState.previousValue.toStatsInt()
- )
-
+ try {
+ withTimeout(SCREEN_EVENT_TIMEOUT) {
+ val event =
+ DisplaySwitchLatencyEvent().withBeforeFields(previousState.toStatsInt())
val displaySwitchTimeMs =
measureTimeMillis(systemClock) {
- try {
- withTimeout(SCREEN_EVENT_TIMEOUT) {
- traceAsync(TAG, "displaySwitch") {
- waitForDisplaySwitch(toFoldableDeviceState)
- }
- }
- } catch (e: TimeoutCancellationException) {
- Log.e(TAG, "Wait for display switch timed out")
+ traceAsync(TAG, "displaySwitch") {
+ waitForDisplaySwitch(newState.toStatsInt())
}
}
-
- displaySwitchLatencyEvent =
- displaySwitchLatencyEvent.withAfterFields(
- toFoldableDeviceState,
- displaySwitchTimeMs.toInt(),
- getCurrentState()
- )
- emit(displaySwitchLatencyEvent)
+ if (previousState == DeviceState.FOLDED) {
+ latencyTracker.onActionEnd(ACTION_SWITCH_DISPLAY_UNFOLD)
+ }
+ logDisplaySwitchEvent(event, newState, displaySwitchTimeMs)
}
+ } catch (e: TimeoutCancellationException) {
+ instantForTrack(TAG) { "tracking timed out" }
+ latencyTracker.onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD)
+ } catch (e: CancellationException) {
+ instantForTrack(TAG) { "new state interrupted, entering cool down" }
+ latencyTracker.onActionCancel(ACTION_SWITCH_DISPLAY_UNFOLD)
+ startCoolDown()
}
- .collect { displaySwitchLatencyLogger.log(it) }
+ }
}
}
+ @OptIn(FlowPreview::class)
+ private fun startCoolDown() {
+ if (isCoolingDown) return
+ isCoolingDown = true
+ applicationScope.launch(context = backgroundDispatcher) {
+ val startTime = systemClock.elapsedRealtime()
+ try {
+ startOrEndEvent.timeout(COOL_DOWN_DURATION).collect()
+ } catch (e: TimeoutCancellationException) {
+ instantForTrack(TAG) {
+ "cool down finished, lasted ${systemClock.elapsedRealtime() - startTime} ms"
+ }
+ isCoolingDown = false
+ }
+ }
+ }
+
+ private fun logDisplaySwitchEvent(
+ event: DisplaySwitchLatencyEvent,
+ toFoldableDeviceState: DeviceState,
+ displaySwitchTimeMs: Long,
+ ) {
+ displaySwitchLatencyLogger.log(
+ event.withAfterFields(
+ toFoldableDeviceState.toStatsInt(),
+ displaySwitchTimeMs.toInt(),
+ getCurrentState(),
+ )
+ )
+ }
+
private fun DeviceState.toStatsInt(): Int =
when (this) {
DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
@@ -152,9 +196,20 @@ constructor(
}
}
+ private fun anyEndEventFlow(): Flow<Any> {
+ val unfoldStatus =
+ unfoldTransitionInteractor.unfoldTransitionStatus.filter { it is TransitionStarted }
+ // dropping first emission as we're only interested in new emissions, not current state
+ val screenOn =
+ powerInteractor.screenPowerState.drop(1).filter { it == ScreenPowerState.SCREEN_ON }
+ val goToSleep =
+ powerInteractor.detailedWakefulness.drop(1).filter { sleepWithScreenOff(it) }
+ return merge(screenOn, goToSleep, unfoldStatus)
+ }
+
private fun shouldWaitForTransitionStart(
toFoldableDeviceState: Int,
- isTransitionEnabled: Boolean
+ isTransitionEnabled: Boolean,
): Boolean = (toFoldableDeviceState != FOLDABLE_DEVICE_STATE_CLOSED && isTransitionEnabled)
private suspend fun waitForScreenTurnedOn() {
@@ -165,12 +220,13 @@ constructor(
private suspend fun waitForGoToSleepWithScreenOff() {
traceAsync(TAG, "waitForGoToSleepWithScreenOff()") {
- powerInteractor.detailedWakefulness
- .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled }
- .first()
+ powerInteractor.detailedWakefulness.filter { sleepWithScreenOff(it) }.first()
}
}
+ private fun sleepWithScreenOff(model: WakefulnessModel) =
+ model.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled
+
private fun getCurrentState(): Int =
when {
isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
@@ -205,7 +261,7 @@ constructor(
private fun DisplaySwitchLatencyEvent.withAfterFields(
toFoldableDeviceState: Int,
displaySwitchTimeMs: Int,
- toState: Int
+ toState: Int,
): DisplaySwitchLatencyEvent {
log {
"toFoldableDeviceState=$toFoldableDeviceState, " +
@@ -217,7 +273,7 @@ constructor(
return copy(
toFoldableDeviceState = toFoldableDeviceState,
latencyMs = displaySwitchTimeMs,
- toState = toState
+ toState = toState,
)
}
@@ -250,14 +306,15 @@ constructor(
val hallSensorToFirstHingeAngleChangeMs: Int = VALUE_UNKNOWN,
val hallSensorToDeviceStateChangeMs: Int = VALUE_UNKNOWN,
val onScreenTurningOnToOnDrawnMs: Int = VALUE_UNKNOWN,
- val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN
+ val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN,
)
companion object {
private const val VALUE_UNKNOWN = -1
private const val TAG = "DisplaySwitchLatency"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
- private val SCREEN_EVENT_TIMEOUT = Duration.ofMillis(15000).toMillis()
+ @VisibleForTesting val SCREEN_EVENT_TIMEOUT = 15.seconds
+ @VisibleForTesting val COOL_DOWN_DURATION = 2.seconds
private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
new file mode 100644
index 000000000000..6ac0bb168f18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/NoCooldownDisplaySwitchLatencyTracker.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 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.unfold
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import android.util.Log
+import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.app.tracing.instantForTrack
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.unfoldLatencyTrackingFix
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import com.android.systemui.util.Compile
+import com.android.systemui.util.Utils.isDeviceFoldable
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.race
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.measureTimeMillis
+import java.time.Duration
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Old version of [DisplaySwitchLatencyTracker] tracking only [DisplaySwitchLatencyEvent]. Which
+ * version is used for tracking depends on [unfoldLatencyTrackingFix] flag.
+ */
+@SysUISingleton
+class NoCooldownDisplaySwitchLatencyTracker
+@Inject
+constructor(
+ private val context: Context,
+ private val deviceStateRepository: DeviceStateRepository,
+ private val powerInteractor: PowerInteractor,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ private val animationStatusRepository: AnimationStatusRepository,
+ private val keyguardInteractor: KeyguardInteractor,
+ @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
+ @Application private val applicationScope: CoroutineScope,
+ private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
+ private val systemClock: SystemClock,
+ private val deviceStateManager: DeviceStateManager,
+) : CoreStartable {
+
+ private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
+ private val isAodEnabled: Boolean
+ get() = keyguardInteractor.isAodAvailable.value
+
+ override fun start() {
+ if (!isDeviceFoldable(context.resources, deviceStateManager)) {
+ return
+ }
+ applicationScope.launch(context = backgroundDispatcher) {
+ deviceStateRepository.state
+ .pairwise()
+ .filter {
+ // Start tracking only when the foldable device is
+ // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
+ // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+ foldableDeviceState ->
+ foldableDeviceState.previousValue == DeviceState.FOLDED ||
+ foldableDeviceState.newValue == DeviceState.FOLDED
+ }
+ .flatMapLatest { foldableDeviceState ->
+ flow {
+ var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
+ val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
+ displaySwitchLatencyEvent =
+ displaySwitchLatencyEvent.withBeforeFields(
+ foldableDeviceState.previousValue.toStatsInt()
+ )
+
+ val displaySwitchTimeMs =
+ measureTimeMillis(systemClock) {
+ try {
+ withTimeout(SCREEN_EVENT_TIMEOUT) {
+ traceAsync(TAG, "displaySwitch") {
+ waitForDisplaySwitch(toFoldableDeviceState)
+ }
+ }
+ } catch (e: TimeoutCancellationException) {
+ Log.e(TAG, "Wait for display switch timed out")
+ }
+ }
+
+ displaySwitchLatencyEvent =
+ displaySwitchLatencyEvent.withAfterFields(
+ toFoldableDeviceState,
+ displaySwitchTimeMs.toInt(),
+ getCurrentState(),
+ )
+ emit(displaySwitchLatencyEvent)
+ }
+ }
+ .collect { displaySwitchLatencyLogger.log(it) }
+ }
+ }
+
+ private fun DeviceState.toStatsInt(): Int =
+ when (this) {
+ DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
+ DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN
+ DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN
+ DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED
+ else -> FOLDABLE_DEVICE_STATE_UNKNOWN
+ }
+
+ private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) {
+ val isTransitionEnabled =
+ unfoldTransitionInteractor.isAvailable &&
+ animationStatusRepository.areAnimationsEnabled().first()
+ if (shouldWaitForTransitionStart(toFoldableDeviceState, isTransitionEnabled)) {
+ traceAsync(TAG, "waitForTransitionStart()") {
+ unfoldTransitionInteractor.waitForTransitionStart()
+ }
+ } else {
+ race({ waitForScreenTurnedOn() }, { waitForGoToSleepWithScreenOff() })
+ }
+ }
+
+ private fun shouldWaitForTransitionStart(
+ toFoldableDeviceState: Int,
+ isTransitionEnabled: Boolean,
+ ): Boolean = (toFoldableDeviceState != FOLDABLE_DEVICE_STATE_CLOSED && isTransitionEnabled)
+
+ private suspend fun waitForScreenTurnedOn() {
+ traceAsync(TAG, "waitForScreenTurnedOn()") {
+ powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ }
+ }
+
+ private suspend fun waitForGoToSleepWithScreenOff() {
+ traceAsync(TAG, "waitForGoToSleepWithScreenOff()") {
+ powerInteractor.detailedWakefulness
+ .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP && !isAodEnabled }
+ .first()
+ }
+ }
+
+ private fun getCurrentState(): Int =
+ when {
+ isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+ isStateScreenOff() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
+ else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN
+ }
+
+ private fun isStateAod(): Boolean = (isAsleepDueToFold() && isAodEnabled)
+
+ private fun isStateScreenOff(): Boolean = (isAsleepDueToFold() && !isAodEnabled)
+
+ private fun isAsleepDueToFold(): Boolean {
+ val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value
+
+ return (lastWakefulnessEvent.isAsleep() &&
+ (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD))
+ }
+
+ private inline fun log(msg: () -> String) {
+ if (DEBUG) Log.d(TAG, msg())
+ }
+
+ private fun DisplaySwitchLatencyEvent.withBeforeFields(
+ fromFoldableDeviceState: Int
+ ): DisplaySwitchLatencyEvent {
+ log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
+ instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }
+
+ return copy(fromFoldableDeviceState = fromFoldableDeviceState)
+ }
+
+ private fun DisplaySwitchLatencyEvent.withAfterFields(
+ toFoldableDeviceState: Int,
+ displaySwitchTimeMs: Int,
+ toState: Int,
+ ): DisplaySwitchLatencyEvent {
+ log {
+ "toFoldableDeviceState=$toFoldableDeviceState, " +
+ "toState=$toState, " +
+ "latencyMs=$displaySwitchTimeMs"
+ }
+ instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" }
+
+ return copy(
+ toFoldableDeviceState = toFoldableDeviceState,
+ latencyMs = displaySwitchTimeMs,
+ toState = toState,
+ )
+ }
+
+ companion object {
+ private const val VALUE_UNKNOWN = -1
+ private const val TAG = "DisplaySwitchLatency"
+ private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
+ private val SCREEN_EVENT_TIMEOUT = Duration.ofMillis(15000).toMillis()
+
+ private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
+ const val FOLDABLE_DEVICE_STATE_CLOSED =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED
+ const val FOLDABLE_DEVICE_STATE_HALF_OPEN =
+ SysUiStatsLog
+ .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED
+ private const val FOLDABLE_DEVICE_STATE_OPEN =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
+ private const val FOLDABLE_DEVICE_STATE_FLIPPED =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index f806a5c52d5a..9248cc801227 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -22,6 +22,7 @@ import android.hardware.devicestate.DeviceStateManager
import android.os.Trace
import android.util.Log
import com.android.internal.util.LatencyTracker
+import com.android.systemui.Flags.unfoldLatencyTrackingFix
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
@@ -63,7 +64,7 @@ constructor(
/** Registers for relevant events only if the device is foldable. */
fun init() {
- if (!isFoldable) {
+ if (unfoldLatencyTrackingFix() || !isFoldable) {
return
}
deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
@@ -85,7 +86,7 @@ constructor(
if (DEBUG) {
Log.d(
TAG,
- "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
)
}
@@ -109,7 +110,7 @@ constructor(
if (DEBUG) {
Log.d(
TAG,
- "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled",
)
}
@@ -161,7 +162,7 @@ constructor(
Log.d(
TAG,
"Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
- "isTransitionEnabled = $isTransitionEnabled"
+ "isTransitionEnabled = $isTransitionEnabled",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index 885a2b0d7305..c2f86a37c6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
@@ -48,6 +49,9 @@ constructor(
val isAvailable: Boolean
get() = repository.isAvailable
+ /** Flow of latest [UnfoldTransitionStatus] changes */
+ val unfoldTransitionStatus: Flow<UnfoldTransitionStatus> = repository.transitionStatus
+
/**
* This mapping emits 1 when the device is completely unfolded and 0.0 when the device is
* completely folded.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 68bffeefb0f0..4d5477052388 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -37,8 +37,6 @@ import android.media.IAudioService;
import android.media.IVolumeController;
import android.media.MediaRouter2Manager;
import android.media.VolumePolicy;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -61,6 +59,7 @@ import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
+import com.android.settingslib.volume.MediaSessions.SessionId;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -1402,12 +1401,13 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
- private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
+ private final HashMap<SessionId, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_REMOTE_START_INDEX;
@Override
- public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
+ public void onRemoteUpdate(
+ SessionId token, String name, MediaSessions.VolumeInfo volumeInfo) {
addStream(token, "onRemoteUpdate");
int stream = 0;
@@ -1415,14 +1415,15 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
stream = mRemoteStreams.get(token);
}
Slog.d(TAG,
- "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
+ "onRemoteUpdate: stream: "
+ + stream + " volume: " + volumeInfo.getCurrentVolume());
boolean changed = mState.states.indexOfKey(stream) < 0;
final StreamState ss = streamStateW(stream);
ss.dynamic = true;
ss.levelMin = 0;
- ss.levelMax = pi.getMaxVolume();
- if (ss.level != pi.getCurrentVolume()) {
- ss.level = pi.getCurrentVolume();
+ ss.levelMax = volumeInfo.getMaxVolume();
+ if (ss.level != volumeInfo.getCurrentVolume()) {
+ ss.level = volumeInfo.getCurrentVolume();
changed = true;
}
if (!Objects.equals(ss.remoteLabel, name)) {
@@ -1437,11 +1438,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
@Override
- public void onRemoteVolumeChanged(Token token, int flags) {
- addStream(token, "onRemoteVolumeChanged");
+ public void onRemoteVolumeChanged(SessionId sessionId, int flags) {
+ addStream(sessionId, "onRemoteVolumeChanged");
int stream = 0;
synchronized (mRemoteStreams) {
- stream = mRemoteStreams.get(token);
+ stream = mRemoteStreams.get(sessionId);
}
final boolean showUI = shouldShowUI(flags);
Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
@@ -1459,7 +1460,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
@Override
- public void onRemoteRemoved(Token token) {
+ public void onRemoteRemoved(SessionId token) {
int stream;
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
@@ -1480,7 +1481,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
public void setStreamVolume(int stream, int level) {
- final Token token = findToken(stream);
+ final SessionId token = findToken(stream);
if (token == null) {
Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
return;
@@ -1488,9 +1489,9 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mMediaSessions.setVolume(token, level);
}
- private Token findToken(int stream) {
+ private SessionId findToken(int stream) {
synchronized (mRemoteStreams) {
- for (Map.Entry<Token, Integer> entry : mRemoteStreams.entrySet()) {
+ for (Map.Entry<SessionId, Integer> entry : mRemoteStreams.entrySet()) {
if (entry.getValue().equals(stream)) {
return entry.getKey();
}
@@ -1499,7 +1500,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
return null;
}
- private void addStream(Token token, String triggeringMethod) {
+ private void addStream(SessionId token, String triggeringMethod) {
synchronized (mRemoteStreams) {
if (!mRemoteStreams.containsKey(token)) {
mRemoteStreams.put(token, mNextStream);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 83b7c1818341..86defff4a120 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -68,7 +68,7 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.volume_dialog)
- requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+ requireViewById<View>(R.id.volume_dialog).repeatWhenAttached {
coroutineScopeTraced("[Volume]dialog") {
val component = componentFactory.create(this)
with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 20a74b027db5..afe3d7bf217a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.dialog.domain.interactor
import android.annotation.SuppressLint
+import android.provider.Settings
import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
@@ -28,8 +30,9 @@ import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityMod
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Visible
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
-import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -43,8 +46,6 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
-
/**
* Handles Volume Dialog visibility state. It might change from several sources:
* - [com.android.systemui.plugins.VolumeDialogController] requests visibility change;
@@ -60,8 +61,11 @@ constructor(
private val tracer: VolumeTracer,
private val repository: VolumeDialogVisibilityRepository,
private val controller: VolumeDialogController,
+ private val secureSettingsRepository: SecureSettingsRepository,
) {
+ private val defaultTimeout = 3.seconds
+
@SuppressLint("SharedFlowCreation")
private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val dialogVisibility: Flow<VolumeDialogVisibilityModel> =
@@ -73,7 +77,14 @@ constructor(
init {
merge(
mutableDismissDialogEvents.mapLatest {
- delay(MAX_DIALOG_SHOW_TIME)
+ delay(
+ secureSettingsRepository
+ .getInt(
+ Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT,
+ defaultTimeout.toInt(DurationUnit.MILLISECONDS),
+ )
+ .milliseconds
+ )
VolumeDialogEventModel.DismissRequested(Events.DISMISS_REASON_TIMEOUT)
},
callbacksInteractor.event,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 3d0c7d64b2a4..92ec4f554548 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -246,16 +246,12 @@ constructor(
uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
) {
val count = uiModel.availableButtons.size
- val selectedButton =
- getChildAt(count - uiModel.currentButtonIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val selectedButton = getChildAt(count - uiModel.currentButtonIndex) as ImageButton
val previousIndex =
uiModel.availableButtons.indexOfFirst {
it.ringerMode == uiModel.drawerState.previousMode
}
- val unselectedButton =
- getChildAt(count - previousIndex)
- .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val unselectedButton = getChildAt(count - previousIndex) as ImageButton
// We only need to execute on roundness animation end and volume dialog background
// progress update once because these changes should be applied once on volume dialog
// background and ringer drawer views.
@@ -306,7 +302,7 @@ constructor(
) {
val count = uiModel.availableButtons.size
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
- val view = getChildAt(count - index)
+ val view = getChildAt(count - index) as ImageButton
val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
view.bindDrawerButton(
@@ -323,37 +319,37 @@ constructor(
onAnimationEnd?.run()
}
- private fun View.bindDrawerButton(
+ private fun ImageButton.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
isOpen: Boolean,
isSelected: Boolean = false,
isAnimated: Boolean = false,
) {
+ // id = buttonViewModel.viewId
+ setSelected(isSelected)
val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
- with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
- setImageResource(buttonViewModel.imageResId)
- contentDescription =
- if (isSelected && !isOpen) {
- context.getString(
- R.string.volume_ringer_drawer_closed_content_description,
- ringerContentDesc,
- )
- } else {
- ringerContentDesc
- }
- if (isSelected && !isAnimated) {
- setBackgroundResource(R.drawable.volume_drawer_selection_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
- background = background.mutate()
- } else if (!isAnimated) {
- setBackgroundResource(R.drawable.volume_ringer_item_bg)
- setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
- background = background.mutate()
- }
- setOnClickListener {
- viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
+ setImageResource(buttonViewModel.imageResId)
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
}
+ if (isSelected && !isAnimated) {
+ setBackgroundResource(R.drawable.volume_drawer_selection_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnPrimary))
+ background = background.mutate()
+ } else if (!isAnimated) {
+ setBackgroundResource(R.drawable.volume_ringer_item_bg)
+ setColorFilter(context.getColor(internalR.color.materialColorOnSurface))
+ background = background.mutate()
+ }
+ setOnClickListener {
+ viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index f2d7d956291c..7cc4bcc4e11c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -74,7 +74,7 @@ constructor(
val insets: MutableStateFlow<WindowInsets> =
MutableStateFlow(WindowInsets.Builder().build())
// Root view of the Volume Dialog.
- val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+ val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog)
animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 2f30b745a4a3..3190d3ae8f16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -91,11 +91,11 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFul
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -122,7 +122,6 @@ import java.util.Optional;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
- private static final int DISPLAY_ID = 0;
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
@@ -233,7 +232,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
- DISPLAY_ID,
mHandler,
mUiBgExecutor,
mVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index fcdda9f13099..9da8e80283b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -44,5 +45,6 @@ var Kosmos.fromDozingTransitionInteractor by
deviceEntryInteractor = deviceEntryInteractor,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
dreamManager = dreamManager,
+ communalSettingsInteractor = communalSettingsInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 5fc31f8b9e10..f2871149de11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.pipeline.data.repository
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -79,9 +80,9 @@ class FakeTileSpecRepository(
with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles }
}
- override val tilesReadFromSetting: Channel<Pair<Set<TileSpec>, Int>> = Channel(capacity = 10)
+ override val tilesUpgradePath: Channel<Pair<TilesUpgradePath, Int>> = Channel(capacity = 10)
- suspend fun sendTilesReadFromSetting(tiles: Set<TileSpec>, userId: Int) {
- tilesReadFromSetting.send(tiles to userId)
+ suspend fun sendTilesFromUpgradePath(upgradePath: TilesUpgradePath, userId: Int) {
+ tilesUpgradePath.send(upgradePath to userId)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 5ff44e5d33c5..c5de02a7281b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -26,7 +26,7 @@ val Kosmos.minimumTilesRepository: MinimumTilesRepository by
Kosmos.Fixture { fakeMinimumTilesRepository }
var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
-val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+var Kosmos.defaultTilesRepository: DefaultTilesRepository by
Kosmos.Fixture { fakeDefaultTilesRepository }
val Kosmos.fakeTileSpecRepository by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 0d6ac4481742..d787e2c190c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -52,7 +52,6 @@ val Kosmos.statusBarNotificationActivityStarter by
Kosmos.Fixture {
StatusBarNotificationActivityStarter(
applicationContext,
- applicationContext.displayId,
fakeExecutorHandler,
fakeExecutor,
notificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index 0d2aa4c79753..888b7e625524 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
import com.android.systemui.volume.dialog.utils.volumeTracer
@@ -30,5 +31,6 @@ val Kosmos.volumeDialogVisibilityInteractor by
volumeTracer,
volumeDialogVisibilityRepository,
volumeDialogController,
+ secureSettingsRepository,
)
}
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index 4e1175034b5b..ab1e9ffe3bfe 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
# Android Accessibility Framework owners
danielnorman@google.com
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index e8dddcb537cd..529a564ea607 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -100,6 +100,13 @@ flag {
}
flag {
+ name: "enable_low_vision_generic_feedback"
+ namespace: "accessibility"
+ description: "Use generic feedback for low vision."
+ bug: "393981463"
+}
+
+flag {
name: "enable_low_vision_hats"
namespace: "accessibility"
description: "Use HaTS for low vision feedback."
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/OWNERS b/services/accessibility/java/com/android/server/accessibility/magnification/OWNERS
new file mode 100644
index 000000000000..ff812ad7e7e6
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 770744.
+
+juchengchou@google.com
+chenjean@google.com
+chihtinglo@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 350ecab1dd5f..d2a5734f323f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -163,6 +163,10 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
+import com.android.server.storage.WatchedVolumeInfo;
+import com.android.server.utils.Watchable;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.Watcher;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
@@ -452,7 +456,7 @@ class StorageManagerService extends IStorageManager.Stub
private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
/** Map from volume ID to disk */
@GuardedBy("mLock")
- private final ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
+ private final WatchedArrayMap<String, WatchedVolumeInfo> mVolumes = new WatchedArrayMap<>();
/** Map from UUID to record */
@GuardedBy("mLock")
@@ -503,9 +507,9 @@ class StorageManagerService extends IStorageManager.Stub
"(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/(?:data|media|obb|sandbox)/)([^/]+)(/.*)?");
- private VolumeInfo findVolumeByIdOrThrow(String id) {
+ private WatchedVolumeInfo findVolumeByIdOrThrow(String id) {
synchronized (mLock) {
- final VolumeInfo vol = mVolumes.get(id);
+ final WatchedVolumeInfo vol = mVolumes.get(id);
if (vol != null) {
return vol;
}
@@ -516,9 +520,9 @@ class StorageManagerService extends IStorageManager.Stub
private VolumeRecord findRecordForPath(String path) {
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
- if (vol.path != null && path.startsWith(vol.path)) {
- return mRecords.get(vol.fsUuid);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.getFsPath() != null && path.startsWith(vol.getFsPath())) {
+ return mRecords.get(vol.getFsUuid());
}
}
}
@@ -764,7 +768,7 @@ class StorageManagerService extends IStorageManager.Stub
break;
}
case H_VOLUME_MOUNT: {
- final VolumeInfo vol = (VolumeInfo) msg.obj;
+ final WatchedVolumeInfo vol = (WatchedVolumeInfo) msg.obj;
if (isMountDisallowed(vol)) {
Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
break;
@@ -774,7 +778,7 @@ class StorageManagerService extends IStorageManager.Stub
break;
}
case H_VOLUME_UNMOUNT: {
- final VolumeInfo vol = (VolumeInfo) msg.obj;
+ final WatchedVolumeInfo vol = (WatchedVolumeInfo) msg.obj;
unmount(vol);
break;
}
@@ -828,7 +832,8 @@ class StorageManagerService extends IStorageManager.Stub
}
case H_VOLUME_STATE_CHANGED: {
final SomeArgs args = (SomeArgs) msg.obj;
- onVolumeStateChangedAsync((VolumeInfo) args.arg1, args.argi1, args.argi2);
+ onVolumeStateChangedAsync((WatchedVolumeInfo) args.arg1, args.argi1,
+ args.argi2);
args.recycle();
break;
}
@@ -892,9 +897,9 @@ class StorageManagerService extends IStorageManager.Stub
synchronized (mLock) {
final int size = mVolumes.size();
for (int i = 0; i < size; i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
- if (vol.mountUserId == userId) {
- vol.mountUserId = UserHandle.USER_NULL;
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.getMountUserId() == userId) {
+ vol.setMountUserId(UserHandle.USER_NULL);
mHandler.obtainMessage(H_VOLUME_UNMOUNT, vol).sendToTarget();
}
}
@@ -1084,7 +1089,7 @@ class StorageManagerService extends IStorageManager.Stub
VolumeInfo.TYPE_PRIVATE, null, null);
internal.state = VolumeInfo.STATE_MOUNTED;
internal.path = Environment.getDataDirectory().getAbsolutePath();
- mVolumes.put(internal.id, internal);
+ mVolumes.put(internal.id, WatchedVolumeInfo.fromVolumeInfo(internal));
}
private void resetIfBootedAndConnected() {
@@ -1242,7 +1247,7 @@ class StorageManagerService extends IStorageManager.Stub
}
}
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
if (vol.isVisibleForUser(userId) && vol.isMountedReadable()) {
final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
@@ -1291,21 +1296,21 @@ class StorageManagerService extends IStorageManager.Stub
}
private void maybeRemountVolumes(int userId) {
- List<VolumeInfo> volumesToRemount = new ArrayList<>();
+ List<WatchedVolumeInfo> volumesToRemount = new ArrayList<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
if (!vol.isPrimary() && vol.isMountedWritable() && vol.isVisible()
&& vol.getMountUserId() != mCurrentUserId) {
// If there's a visible secondary volume mounted,
// we need to update the currentUserId and remount
- vol.mountUserId = mCurrentUserId;
+ vol.setMountUserId(mCurrentUserId);
volumesToRemount.add(vol);
}
}
}
- for (VolumeInfo vol : volumesToRemount) {
+ for (WatchedVolumeInfo vol : volumesToRemount) {
Slog.i(TAG, "Remounting volume for user: " + userId + ". Volume: " + vol);
mHandler.obtainMessage(H_VOLUME_UNMOUNT, vol).sendToTarget();
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
@@ -1317,12 +1322,12 @@ class StorageManagerService extends IStorageManager.Stub
* trying to mount doesn't have the same mount user id as the current user being maintained by
* StorageManagerService and change the mount Id. The checks are same as
* {@link StorageManagerService#maybeRemountVolumes(int)}
- * @param VolumeInfo object to consider for changing the mountId
+ * @param vol {@link WatchedVolumeInfo} object to consider for changing the mountId
*/
- private void updateVolumeMountIdIfRequired(VolumeInfo vol) {
+ private void updateVolumeMountIdIfRequired(WatchedVolumeInfo vol) {
synchronized (mLock) {
if (!vol.isPrimary() && vol.isVisible() && vol.getMountUserId() != mCurrentUserId) {
- vol.mountUserId = mCurrentUserId;
+ vol.setMountUserId(mCurrentUserId);
}
}
}
@@ -1485,20 +1490,21 @@ class StorageManagerService extends IStorageManager.Stub
final DiskInfo disk = mDisks.get(diskId);
final VolumeInfo vol = new VolumeInfo(volId, type, disk, partGuid);
vol.mountUserId = userId;
- mVolumes.put(volId, vol);
- onVolumeCreatedLocked(vol);
+ WatchedVolumeInfo watchedVol = WatchedVolumeInfo.fromVolumeInfo(vol);
+ mVolumes.put(volId, watchedVol);
+ onVolumeCreatedLocked(watchedVol);
}
}
@Override
public void onVolumeStateChanged(String volId, final int newState, final int userId) {
synchronized (mLock) {
- final VolumeInfo vol = mVolumes.get(volId);
+ final WatchedVolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
- final int oldState = vol.state;
- vol.state = newState;
- final VolumeInfo vInfo = new VolumeInfo(vol);
- vInfo.mountUserId = userId;
+ final int oldState = vol.getState();
+ vol.setState(newState);
+ final WatchedVolumeInfo vInfo = new WatchedVolumeInfo(vol);
+ vInfo.setMountUserId(userId);
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vInfo;
args.argi1 = oldState;
@@ -1513,11 +1519,11 @@ class StorageManagerService extends IStorageManager.Stub
public void onVolumeMetadataChanged(String volId, String fsType, String fsUuid,
String fsLabel) {
synchronized (mLock) {
- final VolumeInfo vol = mVolumes.get(volId);
+ final WatchedVolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
- vol.fsType = fsType;
- vol.fsUuid = fsUuid;
- vol.fsLabel = fsLabel;
+ vol.setFsType(fsType);
+ vol.setFsUuid(fsUuid);
+ vol.setFsLabel(fsLabel);
}
}
}
@@ -1525,9 +1531,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void onVolumePathChanged(String volId, String path) {
synchronized (mLock) {
- final VolumeInfo vol = mVolumes.get(volId);
+ final WatchedVolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
- vol.path = path;
+ vol.setFsPath(path);
}
}
}
@@ -1535,24 +1541,24 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void onVolumeInternalPathChanged(String volId, String internalPath) {
synchronized (mLock) {
- final VolumeInfo vol = mVolumes.get(volId);
+ final WatchedVolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
- vol.internalPath = internalPath;
+ vol.setInternalPath(internalPath);
}
}
}
@Override
public void onVolumeDestroyed(String volId) {
- VolumeInfo vol = null;
+ WatchedVolumeInfo vol = null;
synchronized (mLock) {
vol = mVolumes.remove(volId);
}
if (vol != null) {
- mStorageSessionController.onVolumeRemove(vol);
+ mStorageSessionController.onVolumeRemove(vol.getImmutableVolumeInfo());
try {
- if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
}
} catch (Installer.InstallerException e) {
@@ -1566,7 +1572,7 @@ class StorageManagerService extends IStorageManager.Stub
private void onDiskScannedLocked(DiskInfo disk) {
int volumeCount = 0;
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
if (Objects.equals(disk.id, vol.getDiskId())) {
volumeCount++;
}
@@ -1589,19 +1595,19 @@ class StorageManagerService extends IStorageManager.Stub
}
@GuardedBy("mLock")
- private void onVolumeCreatedLocked(VolumeInfo vol) {
+ private void onVolumeCreatedLocked(WatchedVolumeInfo vol) {
final ActivityManagerInternal amInternal =
LocalServices.getService(ActivityManagerInternal.class);
- if (vol.mountUserId >= 0 && !amInternal.isUserRunning(vol.mountUserId, 0)) {
+ if (vol.getMountUserId() >= 0 && !amInternal.isUserRunning(vol.getMountUserId(), 0)) {
Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "
- + Integer.toString(vol.mountUserId) + " is no longer running.");
+ + Integer.toString(vol.getMountUserId()) + " is no longer running.");
return;
}
- if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ if (vol.getType() == VolumeInfo.TYPE_EMULATED) {
final Context volumeUserContext = mContext.createContextAsUser(
- UserHandle.of(vol.mountUserId), 0);
+ UserHandle.of(vol.getMountUserId()), 0);
boolean isMediaSharedWithParent =
(volumeUserContext != null) ? volumeUserContext.getSystemService(
@@ -1611,60 +1617,60 @@ class StorageManagerService extends IStorageManager.Stub
// should not be skipped even if media provider instance is not running in that user
// space
if (!isMediaSharedWithParent
- && !mStorageSessionController.supportsExternalStorage(vol.mountUserId)) {
+ && !mStorageSessionController.supportsExternalStorage(vol.getMountUserId())) {
Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "
- + Integer.toString(vol.mountUserId)
+ + Integer.toString(vol.getMountUserId())
+ " does not support external storage.");
return;
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
- final VolumeInfo privateVol = storage.findPrivateForEmulated(vol);
+ final VolumeInfo privateVol = storage.findPrivateForEmulated(vol.getVolumeInfo());
if ((Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, mPrimaryStorageUuid)
&& VolumeInfo.ID_PRIVATE_INTERNAL.equals(privateVol.id))
- || Objects.equals(privateVol.fsUuid, mPrimaryStorageUuid)) {
+ || Objects.equals(privateVol.getFsUuid(), mPrimaryStorageUuid)) {
Slog.v(TAG, "Found primary storage at " + vol);
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_PRIMARY);
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE);
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
}
- } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
+ } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
// TODO: only look at first public partition
if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
- && vol.disk.isDefaultPrimary()) {
+ && vol.getDisk().isDefaultPrimary()) {
Slog.v(TAG, "Found primary storage at " + vol);
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_PRIMARY);
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE);
}
// Adoptable public disks are visible to apps, since they meet
// public API requirement of being in a stable location.
- if (vol.disk.isAdoptable()) {
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+ if (vol.getDisk().isAdoptable()) {
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE);
}
- vol.mountUserId = mCurrentUserId;
+ vol.setMountUserId(mCurrentUserId);
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ } else if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- } else if (vol.type == VolumeInfo.TYPE_STUB) {
- if (vol.disk.isStubVisible()) {
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+ } else if (vol.getType() == VolumeInfo.TYPE_STUB) {
+ if (vol.getDisk().isStubVisible()) {
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE);
} else {
- vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_READ;
+ vol.setMountFlags(vol.getMountFlags() | VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_READ);
}
- vol.mountUserId = mCurrentUserId;
+ vol.setMountUserId(mCurrentUserId);
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
} else {
Slog.d(TAG, "Skipping automatic mounting of " + vol);
}
}
- private boolean isBroadcastWorthy(VolumeInfo vol) {
+ private boolean isBroadcastWorthy(WatchedVolumeInfo vol) {
switch (vol.getType()) {
case VolumeInfo.TYPE_PRIVATE:
case VolumeInfo.TYPE_PUBLIC:
@@ -1691,8 +1697,8 @@ class StorageManagerService extends IStorageManager.Stub
}
@GuardedBy("mLock")
- private void onVolumeStateChangedLocked(VolumeInfo vol, int newState) {
- if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ private void onVolumeStateChangedLocked(WatchedVolumeInfo vol, int newState) {
+ if (vol.getType() == VolumeInfo.TYPE_EMULATED) {
if (newState != VolumeInfo.STATE_MOUNTED) {
mFuseMountedUser.remove(vol.getMountUserId());
} else if (mVoldAppDataIsolationEnabled){
@@ -1741,7 +1747,7 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) {
+ private void onVolumeStateChangedAsync(WatchedVolumeInfo vol, int oldState, int newState) {
if (newState == VolumeInfo.STATE_MOUNTED) {
// Private volumes can be unmounted and re-mounted even after a user has
// been unlocked; on devices that support encryption keys tied to the filesystem,
@@ -1751,7 +1757,7 @@ class StorageManagerService extends IStorageManager.Stub
} catch (Exception e) {
// Unusable partition, unmount.
try {
- mVold.unmount(vol.id);
+ mVold.unmount(vol.getId());
} catch (Exception ee) {
Slog.wtf(TAG, ee);
}
@@ -1762,20 +1768,20 @@ class StorageManagerService extends IStorageManager.Stub
synchronized (mLock) {
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
- if (!TextUtils.isEmpty(vol.fsUuid)) {
- VolumeRecord rec = mRecords.get(vol.fsUuid);
+ if (!TextUtils.isEmpty(vol.getFsUuid())) {
+ VolumeRecord rec = mRecords.get(vol.getFsUuid());
if (rec == null) {
- rec = new VolumeRecord(vol.type, vol.fsUuid);
- rec.partGuid = vol.partGuid;
+ rec = new VolumeRecord(vol.getType(), vol.getFsUuid());
+ rec.partGuid = vol.getPartGuid();
rec.createdMillis = System.currentTimeMillis();
- if (vol.type == VolumeInfo.TYPE_PRIVATE) {
- rec.nickname = vol.disk.getDescription();
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
+ rec.nickname = vol.getDisk().getDescription();
}
mRecords.put(rec.fsUuid, rec);
} else {
// Handle upgrade case where we didn't store partition GUID
if (TextUtils.isEmpty(rec.partGuid)) {
- rec.partGuid = vol.partGuid;
+ rec.partGuid = vol.getPartGuid();
}
}
@@ -1788,7 +1794,7 @@ class StorageManagerService extends IStorageManager.Stub
// before notifying other listeners.
// Intentionally called without the mLock to avoid deadlocking from the Storage Service.
try {
- mStorageSessionController.notifyVolumeStateChanged(vol);
+ mStorageSessionController.notifyVolumeStateChanged(vol.getImmutableVolumeInfo());
} catch (ExternalStorageServiceException e) {
Log.e(TAG, "Failed to notify volume state changed to the Storage Service", e);
}
@@ -1799,9 +1805,9 @@ class StorageManagerService extends IStorageManager.Stub
// processes that receive the intent unnecessarily.
if (mBootCompleted && isBroadcastWorthy(vol)) {
final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
- intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
+ intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
- intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
+ intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
@@ -1826,8 +1832,8 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- if ((vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_STUB)
- && vol.state == VolumeInfo.STATE_EJECTING) {
+ if ((vol.getType() == VolumeInfo.TYPE_PUBLIC || vol.getType() == VolumeInfo.TYPE_STUB)
+ && vol.getState() == VolumeInfo.STATE_EJECTING) {
// TODO: this should eventually be handled by new ObbVolume state changes
/*
* Some OBBs might have been unmounted when this volume was
@@ -1835,7 +1841,7 @@ class StorageManagerService extends IStorageManager.Stub
* remove those from the list of mounted OBBS.
*/
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
- OBB_FLUSH_MOUNT_STATE, vol.path));
+ OBB_FLUSH_MOUNT_STATE, vol.getFsPath()));
}
maybeLogMediaMount(vol, newState);
}
@@ -1860,7 +1866,7 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private void maybeLogMediaMount(VolumeInfo vol, int newState) {
+ private void maybeLogMediaMount(WatchedVolumeInfo vol, int newState) {
if (!SecurityLog.isLoggingEnabled()) {
return;
}
@@ -1875,10 +1881,10 @@ class StorageManagerService extends IStorageManager.Stub
if (newState == VolumeInfo.STATE_MOUNTED
|| newState == VolumeInfo.STATE_MOUNTED_READ_ONLY) {
- SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_MOUNT, vol.path, label);
+ SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_MOUNT, vol.getFsPath(), label);
} else if (newState == VolumeInfo.STATE_UNMOUNTED
|| newState == VolumeInfo.STATE_BAD_REMOVAL) {
- SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_UNMOUNT, vol.path, label);
+ SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_UNMOUNT, vol.getFsPath(), label);
}
}
@@ -1920,18 +1926,18 @@ class StorageManagerService extends IStorageManager.Stub
/**
* Decide if volume is mountable per device policies.
*/
- private boolean isMountDisallowed(VolumeInfo vol) {
+ private boolean isMountDisallowed(WatchedVolumeInfo vol) {
UserManager userManager = mContext.getSystemService(UserManager.class);
boolean isUsbRestricted = false;
- if (vol.disk != null && vol.disk.isUsb()) {
+ if (vol.getDisk() != null && vol.getDisk().isUsb()) {
isUsbRestricted = userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER,
Binder.getCallingUserHandle());
}
boolean isTypeRestricted = false;
- if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE
- || vol.type == VolumeInfo.TYPE_STUB) {
+ if (vol.getType() == VolumeInfo.TYPE_PUBLIC || vol.getType() == VolumeInfo.TYPE_PRIVATE
+ || vol.getType() == VolumeInfo.TYPE_STUB) {
isTypeRestricted = userManager
.hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
Binder.getCallingUserHandle());
@@ -1967,6 +1973,13 @@ class StorageManagerService extends IStorageManager.Stub
mContext = context;
mCallbacks = new Callbacks(FgThread.get().getLooper());
+ mVolumes.registerObserver(new Watcher() {
+ @Override
+ public void onChange(Watchable what) {
+ // When we change the list or any volume contained in it, invalidate the cache
+ StorageManager.invalidateVolumeListCache();
+ }
+ });
HandlerThread hthread = new HandlerThread(TAG);
hthread.start();
mHandler = new StorageManagerServiceHandler(hthread.getLooper());
@@ -2339,7 +2352,7 @@ class StorageManagerService extends IStorageManager.Stub
super.mount_enforcePermission();
- final VolumeInfo vol = findVolumeByIdOrThrow(volId);
+ final WatchedVolumeInfo vol = findVolumeByIdOrThrow(volId);
if (isMountDisallowed(vol)) {
throw new SecurityException("Mounting " + volId + " restricted by policy");
}
@@ -2365,23 +2378,24 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private void mount(VolumeInfo vol) {
+ private void mount(WatchedVolumeInfo vol) {
try {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SMS.mount: " + vol.id);
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "SMS.mount: " + vol.getId());
// TODO(b/135341433): Remove cautious logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
extendWatchdogTimeout("#mount might be slow");
- mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
+ mVold.mount(vol.getId(), vol.getMountFlags(), vol.getMountUserId(),
+ new IVoldMountCallback.Stub() {
@Override
public boolean onVolumeChecking(FileDescriptor fd, String path,
String internalPath) {
- vol.path = path;
- vol.internalPath = internalPath;
+ vol.setFsPath(path);
+ vol.setInternalPath(internalPath);
ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
try {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- "SMS.startFuseFileSystem: " + vol.id);
- mStorageSessionController.onVolumeMount(pfd, vol);
+ "SMS.startFuseFileSystem: " + vol.getId());
+ mStorageSessionController.onVolumeMount(pfd, vol.getImmutableVolumeInfo());
return true;
} catch (ExternalStorageServiceException e) {
Slog.e(TAG, "Failed to mount volume " + vol, e);
@@ -2416,21 +2430,21 @@ class StorageManagerService extends IStorageManager.Stub
super.unmount_enforcePermission();
- final VolumeInfo vol = findVolumeByIdOrThrow(volId);
+ final WatchedVolumeInfo vol = findVolumeByIdOrThrow(volId);
unmount(vol);
}
- private void unmount(VolumeInfo vol) {
+ private void unmount(WatchedVolumeInfo vol) {
try {
try {
- if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
}
} catch (Installer.InstallerException e) {
Slog.e(TAG, "Failed unmount mirror data", e);
}
- mVold.unmount(vol.id);
- mStorageSessionController.onVolumeUnmount(vol);
+ mVold.unmount(vol.getId());
+ mStorageSessionController.onVolumeUnmount(vol.getImmutableVolumeInfo());
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -2442,10 +2456,10 @@ class StorageManagerService extends IStorageManager.Stub
super.format_enforcePermission();
- final VolumeInfo vol = findVolumeByIdOrThrow(volId);
- final String fsUuid = vol.fsUuid;
+ final WatchedVolumeInfo vol = findVolumeByIdOrThrow(volId);
+ final String fsUuid = vol.getFsUuid();
try {
- mVold.format(vol.id, "auto");
+ mVold.format(vol.getId(), "auto");
// After a successful format above, we should forget about any
// records for the old partition, since it'll never appear again
@@ -3105,7 +3119,7 @@ class StorageManagerService extends IStorageManager.Stub
private void warnOnNotMounted() {
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
if (vol.isPrimary() && vol.isMountedWritable()) {
// Cool beans, we have a mounted primary volume
return;
@@ -3392,8 +3406,8 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private void prepareUserStorageIfNeeded(VolumeInfo vol) throws Exception {
- if (vol.type != VolumeInfo.TYPE_PRIVATE) {
+ private void prepareUserStorageIfNeeded(WatchedVolumeInfo vol) throws Exception {
+ if (vol.getType() != VolumeInfo.TYPE_PRIVATE) {
return;
}
@@ -3411,7 +3425,7 @@ class StorageManagerService extends IStorageManager.Stub
continue;
}
- prepareUserStorageInternal(vol.fsUuid, user.id, flags);
+ prepareUserStorageInternal(vol.getFsUuid(), user.id, flags);
}
}
@@ -3960,7 +3974,7 @@ class StorageManagerService extends IStorageManager.Stub
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final String volId = mVolumes.keyAt(i);
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
switch (vol.getType()) {
case VolumeInfo.TYPE_PUBLIC:
case VolumeInfo.TYPE_STUB:
@@ -4112,7 +4126,7 @@ class StorageManagerService extends IStorageManager.Stub
synchronized (mLock) {
final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
for (int i = 0; i < mVolumes.size(); i++) {
- res[i] = mVolumes.valueAt(i);
+ res[i] = mVolumes.valueAt(i).getVolumeInfo();
}
return res;
}
@@ -4708,7 +4722,8 @@ class StorageManagerService extends IStorageManager.Stub
break;
}
case MSG_VOLUME_STATE_CHANGED: {
- callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
+ VolumeInfo volInfo = ((WatchedVolumeInfo) args.arg1).getVolumeInfo();
+ callback.onVolumeStateChanged(volInfo, args.argi2, args.argi3);
break;
}
case MSG_VOLUME_RECORD_CHANGED: {
@@ -4738,7 +4753,7 @@ class StorageManagerService extends IStorageManager.Stub
obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
}
- private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ private void notifyVolumeStateChanged(WatchedVolumeInfo vol, int oldState, int newState) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vol.clone();
args.argi2 = oldState;
@@ -4790,8 +4805,8 @@ class StorageManagerService extends IStorageManager.Stub
pw.println("Volumes:");
pw.increaseIndent();
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
- if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) continue;
vol.dump(pw);
}
pw.decreaseIndent();
@@ -5088,7 +5103,7 @@ class StorageManagerService extends IStorageManager.Stub
final List<String> primaryVolumeIds = new ArrayList<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
- final VolumeInfo vol = mVolumes.valueAt(i);
+ final WatchedVolumeInfo vol = mVolumes.valueAt(i);
if (vol.isPrimary()) {
primaryVolumeIds.add(vol.getId());
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6cca7d16842a..cce29592d912 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -8302,8 +8302,6 @@ public final class ActiveServices {
if ((allowWiu == REASON_DENIED) || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
- // We store them to compare the old and new while-in-use logics to each other.
- // (They're not used for any other purposes.)
if (allowWiu == REASON_DENIED) {
allowWiu = allowWhileInUse;
}
@@ -8706,6 +8704,7 @@ public final class ActiveServices {
+ ",duration:" + tempAllowListReason.mDuration
+ ",callingUid:" + tempAllowListReason.mCallingUid))
+ ">"
+ + "; allowWiu:" + allowWhileInUse
+ "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ "; startForegroundCount:" + r.mStartForegroundCount
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6f79f7073b89..b48d0a6ed547 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -15082,11 +15082,13 @@ public class AudioService extends IAudioService.Stub
final String key = "additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
@@ -15112,11 +15114,13 @@ public class AudioService extends IAudioService.Stub
final String key = "max_additional_output_device_delay";
final String reply = AudioSystem.getParameters(
key + "=" + device.getInternalType() + "," + device.getAddress());
- long delayMillis;
- try {
- delayMillis = Long.parseLong(reply.substring(key.length() + 1));
- } catch (NullPointerException e) {
- delayMillis = 0;
+ long delayMillis = 0;
+ if (reply.contains(key)) {
+ try {
+ delayMillis = Long.parseLong(reply.substring(key.length() + 1));
+ } catch (NullPointerException e) {
+ delayMillis = 0;
+ }
}
return delayMillis;
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index a1e8f08db0a6..aab2760dbc66 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -122,11 +122,6 @@ public class DisplayManagerFlags {
Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
Flags::alwaysRotateDisplayDevice);
- private final FlagState mRefreshRateVotingTelemetry = new FlagState(
- Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
- Flags::refreshRateVotingTelemetry
- );
-
private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
Flags::enablePixelAnisotropyCorrection
@@ -403,10 +398,6 @@ public class DisplayManagerFlags {
return mAlwaysRotateDisplayDevice.isEnabled();
}
- public boolean isRefreshRateVotingTelemetryEnabled() {
- return mRefreshRateVotingTelemetry.isEnabled();
- }
-
public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
return mPixelAnisotropyCorrectionEnabled.isEnabled();
}
@@ -626,7 +617,6 @@ public class DisplayManagerFlags {
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mAlwaysRotateDisplayDevice);
- pw.println(" " + mRefreshRateVotingTelemetry);
pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mIdleScreenRefreshRateTimeout);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index cc0bbde370fe..8211febade60 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -191,14 +191,6 @@ flag {
}
flag {
- name: "refresh_rate_voting_telemetry"
- namespace: "display_manager"
- description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
- bug: "310029108"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_pixel_anisotropy_correction"
namespace: "display_manager"
description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 1dd4a9b93277..c37733b05fba 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -229,8 +229,7 @@ public class DisplayModeDirector {
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
- mVotesStatsReporter = injector.getVotesStatsReporter(
- displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
+ mVotesStatsReporter = injector.getVotesStatsReporter();
mSupportedModesByDisplay = new SparseArray<>();
mAppSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
@@ -3141,7 +3140,7 @@ public class DisplayModeDirector {
SensorManagerInternal getSensorManagerInternal();
@Nullable
- VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
+ VotesStatsReporter getVotesStatsReporter();
}
@VisibleForTesting
@@ -3281,10 +3280,9 @@ public class DisplayModeDirector {
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
// if frame rate override supported, renderRates will be ignored in mode selection
- return new VotesStatsReporter(supportsFrameRateOverride(),
- refreshRateVotingTelemetryEnabled);
+ return new VotesStatsReporter(supportsFrameRateOverride());
}
private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index 7562a525b5f6..d3d49c272338 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -36,13 +36,11 @@ class VotesStatsReporter {
private static final String TAG = "VotesStatsReporter";
private static final int REFRESH_RATE_NOT_LIMITED = 1000;
private final boolean mIgnoredRenderRate;
- private final boolean mFrameworkStatsLogReportingEnabled;
private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
- public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+ VotesStatsReporter(boolean ignoreRenderRate) {
mIgnoredRenderRate = ignoreRenderRate;
- mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
}
void reportVoteChanged(int displayId, int priority, @Nullable Vote vote) {
@@ -57,29 +55,22 @@ class VotesStatsReporter {
int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
- maxRefreshRate, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+ maxRefreshRate, -1);
}
private void reportVoteRemoved(int displayId, int priority) {
Trace.traceCounter(Trace.TRACE_TAG_POWER,
TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
- if (mFrameworkStatsLogReportingEnabled) {
- FrameworkStatsLog.write(
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
- DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
- }
+ FrameworkStatsLog.write(
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+ DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
}
void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
SparseArray<Vote> votes) {
- if (!mFrameworkStatsLogReportingEnabled) {
- return;
- }
int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
if (priority < mLastMinPriorityReported && priority < minPriority) {
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index e2889fa9cbf6..18bccd8411d7 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -91,7 +91,7 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor
updateAttributes = true;
}
if (restrictAudioAttributesAlarm()
- && record.getNotification().category != CATEGORY_ALARM
+ && !CATEGORY_ALARM.equals(record.getNotification().category)
&& attributes.getUsage() == AudioAttributes.USAGE_ALARM) {
updateAttributes = true;
}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index fa4bf228c683..7a31a0006bb9 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -3,5 +3,6 @@
include /core/java/android/security/OWNERS
per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
+per-file *CertificateRevocationStatus* = file:/core/java/android/security/attestationverification/OWNERS
per-file FileIntegrity*.java = victorhsieh@google.com
per-file KeyChainSystemService.java = file:platform/packages/apps/KeyChain:/OWNERS
diff --git a/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java
new file mode 100644
index 000000000000..9d60a576d9bc
--- /dev/null
+++ b/services/core/java/com/android/server/storage/ImmutableVolumeInfo.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.content.Context;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.File;
+
+/**
+ * An immutable version of {@link VolumeInfo} with only getters.
+ *
+ * @hide
+ */
+public final class ImmutableVolumeInfo {
+ private final VolumeInfo mVolumeInfo;
+
+ private ImmutableVolumeInfo(VolumeInfo volumeInfo) {
+ mVolumeInfo = new VolumeInfo(volumeInfo);
+ }
+
+ public static ImmutableVolumeInfo fromVolumeInfo(VolumeInfo info) {
+ return new ImmutableVolumeInfo(info);
+ }
+
+ public ImmutableVolumeInfo clone() {
+ return fromVolumeInfo(mVolumeInfo.clone());
+ }
+
+ public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
+ return mVolumeInfo.buildStorageVolume(context, userId, reportUnmounted);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ mVolumeInfo.dump(pw);
+ }
+
+ public DiskInfo getDisk() {
+ return mVolumeInfo.getDisk();
+ }
+
+ public String getDiskId() {
+ return mVolumeInfo.getDiskId();
+ }
+
+ public String getFsLabel() {
+ return mVolumeInfo.fsLabel;
+ }
+
+ public String getFsPath() {
+ return mVolumeInfo.path;
+ }
+
+ public String getFsType() {
+ return mVolumeInfo.fsType;
+ }
+
+ public String getFsUuid() {
+ return mVolumeInfo.fsUuid;
+ }
+
+ public String getId() {
+ return mVolumeInfo.id;
+ }
+
+ public File getInternalPath() {
+ return mVolumeInfo.getInternalPath();
+ }
+
+ public int getMountFlags() {
+ return mVolumeInfo.mountFlags;
+ }
+
+ public int getMountUserId() {
+ return mVolumeInfo.mountUserId;
+ }
+
+ public String getPartGuid() {
+ return mVolumeInfo.partGuid;
+ }
+
+ public File getPath() {
+ return mVolumeInfo.getPath();
+ }
+
+ public int getState() {
+ return mVolumeInfo.state;
+ }
+
+ public int getType() {
+ return mVolumeInfo.type;
+ }
+
+ public VolumeInfo getVolumeInfo() {
+ return new VolumeInfo(mVolumeInfo); // Return a copy, not the original
+ }
+
+ public boolean isMountedReadable() {
+ return mVolumeInfo.isMountedReadable();
+ }
+
+ public boolean isMountedWritable() {
+ return mVolumeInfo.isMountedWritable();
+ }
+
+ public boolean isPrimary() {
+ return mVolumeInfo.isPrimary();
+ }
+
+ public boolean isVisible() {
+ return mVolumeInfo.isVisible();
+ }
+
+ public boolean isVisibleForUser(int userId) {
+ return mVolumeInfo.isVisibleForUser(userId);
+ }
+
+ public boolean isVisibleForWrite(int userId) {
+ return mVolumeInfo.isVisibleForWrite(userId);
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index b9c9b64cd2c6..342b864c6473 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -45,6 +45,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.storage.ImmutableVolumeInfo;
import java.util.Objects;
@@ -79,18 +80,18 @@ public final class StorageSessionController {
* @param vol for which the storage session has to be started
* @return userId for connection for this volume
*/
- public int getConnectionUserIdForVolume(VolumeInfo vol) {
+ public int getConnectionUserIdForVolume(ImmutableVolumeInfo vol) {
final Context volumeUserContext = mContext.createContextAsUser(
- UserHandle.of(vol.mountUserId), 0);
+ UserHandle.of(vol.getMountUserId()), 0);
boolean isMediaSharedWithParent = volumeUserContext.getSystemService(
UserManager.class).isMediaSharedWithParent();
- UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId);
+ UserInfo userInfo = mUserManager.getUserInfo(vol.getMountUserId());
if (userInfo != null && isMediaSharedWithParent) {
// Clones use the same connection as their parent
return userInfo.profileGroupId;
} else {
- return vol.mountUserId;
+ return vol.getMountUserId();
}
}
@@ -108,7 +109,7 @@ public final class StorageSessionController {
* @throws ExternalStorageServiceException if the session fails to start
* @throws IllegalStateException if a session has already been created for {@code vol}
*/
- public void onVolumeMount(ParcelFileDescriptor deviceFd, VolumeInfo vol)
+ public void onVolumeMount(ParcelFileDescriptor deviceFd, ImmutableVolumeInfo vol)
throws ExternalStorageServiceException {
if (!shouldHandle(vol)) {
return;
@@ -144,7 +145,8 @@ public final class StorageSessionController {
*
* @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService
*/
- public void notifyVolumeStateChanged(VolumeInfo vol) throws ExternalStorageServiceException {
+ public void notifyVolumeStateChanged(ImmutableVolumeInfo vol)
+ throws ExternalStorageServiceException {
if (!shouldHandle(vol)) {
return;
}
@@ -214,7 +216,7 @@ public final class StorageSessionController {
* @return the connection that was removed or {@code null} if nothing was removed
*/
@Nullable
- public StorageUserConnection onVolumeRemove(VolumeInfo vol) {
+ public StorageUserConnection onVolumeRemove(ImmutableVolumeInfo vol) {
if (!shouldHandle(vol)) {
return null;
}
@@ -246,7 +248,7 @@ public final class StorageSessionController {
*
* Call {@link #onVolumeRemove} to remove the connection without waiting for exit
*/
- public void onVolumeUnmount(VolumeInfo vol) {
+ public void onVolumeUnmount(ImmutableVolumeInfo vol) {
String sessionId = vol.getId();
final long token = Binder.clearCallingIdentity();
try {
@@ -457,9 +459,9 @@ public final class StorageSessionController {
* Returns {@code true} if {@code vol} is an emulated or visible public volume,
* {@code false} otherwise
**/
- public static boolean isEmulatedOrPublic(VolumeInfo vol) {
- return vol.type == VolumeInfo.TYPE_EMULATED
- || (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
+ public static boolean isEmulatedOrPublic(ImmutableVolumeInfo vol) {
+ return vol.getType() == VolumeInfo.TYPE_EMULATED
+ || (vol.getType() == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
}
/** Exception thrown when communication with the {@link ExternalStorageService} fails. */
@@ -477,11 +479,11 @@ public final class StorageSessionController {
}
}
- private static boolean isSupportedVolume(VolumeInfo vol) {
- return isEmulatedOrPublic(vol) || vol.type == VolumeInfo.TYPE_STUB;
+ private static boolean isSupportedVolume(ImmutableVolumeInfo vol) {
+ return isEmulatedOrPublic(vol) || vol.getType() == VolumeInfo.TYPE_STUB;
}
- private boolean shouldHandle(@Nullable VolumeInfo vol) {
+ private boolean shouldHandle(@Nullable ImmutableVolumeInfo vol) {
return !mIsResetting && (vol == null || isSupportedVolume(vol));
}
diff --git a/services/core/java/com/android/server/storage/WatchedVolumeInfo.java b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
new file mode 100644
index 000000000000..4124cfb4f092
--- /dev/null
+++ b/services/core/java/com/android/server/storage/WatchedVolumeInfo.java
@@ -0,0 +1,206 @@
+/*
+ * 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.server.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.utils.Watchable;
+import com.android.server.utils.WatchableImpl;
+
+import java.io.File;
+
+/**
+ * A wrapper for {@link VolumeInfo} implementing the {@link Watchable} interface.
+ *
+ * The {@link VolumeInfo} class itself cannot safely implement Watchable, because it has several
+ * UnsupportedAppUsage annotations and public fields, which allow it to be modified without
+ * notifying watchers.
+ *
+ * @hide
+ */
+public class WatchedVolumeInfo extends WatchableImpl {
+ private final VolumeInfo mVolumeInfo;
+
+ private WatchedVolumeInfo(VolumeInfo volumeInfo) {
+ mVolumeInfo = volumeInfo;
+ }
+
+ public WatchedVolumeInfo(WatchedVolumeInfo watchedVolumeInfo) {
+ mVolumeInfo = new VolumeInfo(watchedVolumeInfo.mVolumeInfo);
+ }
+
+ public static WatchedVolumeInfo fromVolumeInfo(VolumeInfo info) {
+ return new WatchedVolumeInfo(info);
+ }
+
+ /**
+ * Returns a copy of the embedded VolumeInfo object, to be used by components
+ * that just need it for retrieving some state from it.
+ *
+ * @return A copy of the embedded VolumeInfo object
+ */
+
+ public WatchedVolumeInfo clone() {
+ return fromVolumeInfo(mVolumeInfo.clone());
+ }
+
+ public ImmutableVolumeInfo getImmutableVolumeInfo() {
+ return ImmutableVolumeInfo.fromVolumeInfo(mVolumeInfo);
+ }
+
+ public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
+ return mVolumeInfo.buildStorageVolume(context, userId, reportUnmounted);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ mVolumeInfo.dump(pw);
+ }
+
+ public DiskInfo getDisk() {
+ return mVolumeInfo.getDisk();
+ }
+
+ public String getDiskId() {
+ return mVolumeInfo.getDiskId();
+ }
+
+ public String getFsLabel() {
+ return mVolumeInfo.fsLabel;
+ }
+
+ public void setFsLabel(String fsLabel) {
+ mVolumeInfo.fsLabel = fsLabel;
+ dispatchChange(this);
+ }
+
+ public String getFsPath() {
+ return mVolumeInfo.path;
+ }
+
+ public void setFsPath(String path) {
+ mVolumeInfo.path = path;
+ dispatchChange(this);
+ }
+
+ public String getFsType() {
+ return mVolumeInfo.fsType;
+ }
+
+ public void setFsType(String fsType) {
+ mVolumeInfo.fsType = fsType;
+ dispatchChange(this);
+ }
+
+ public @Nullable String getFsUuid() {
+ return mVolumeInfo.fsUuid;
+ }
+
+ public void setFsUuid(String fsUuid) {
+ mVolumeInfo.fsUuid = fsUuid;
+ dispatchChange(this);
+ }
+
+ public @NonNull String getId() {
+ return mVolumeInfo.id;
+ }
+
+ public File getInternalPath() {
+ return mVolumeInfo.getInternalPath();
+ }
+
+ public void setInternalPath(String internalPath) {
+ mVolumeInfo.internalPath = internalPath;
+ dispatchChange(this);
+ }
+
+ public int getMountFlags() {
+ return mVolumeInfo.mountFlags;
+ }
+
+ public void setMountFlags(int mountFlags) {
+ mVolumeInfo.mountFlags = mountFlags;
+ dispatchChange(this);
+ }
+
+ public int getMountUserId() {
+ return mVolumeInfo.mountUserId;
+ }
+
+ public void setMountUserId(int mountUserId) {
+ mVolumeInfo.mountUserId = mountUserId;
+ dispatchChange(this);
+ }
+
+ public String getPartGuid() {
+ return mVolumeInfo.partGuid;
+ }
+
+ public File getPath() {
+ return mVolumeInfo.getPath();
+ }
+
+ public int getState() {
+ return mVolumeInfo.state;
+ }
+
+ public int getState(int state) {
+ return mVolumeInfo.state;
+ }
+
+ public void setState(int state) {
+ mVolumeInfo.state = state;
+ dispatchChange(this);
+ }
+
+ public int getType() {
+ return mVolumeInfo.type;
+ }
+
+ public VolumeInfo getVolumeInfo() {
+ return new VolumeInfo(mVolumeInfo);
+ }
+
+ public boolean isMountedReadable() {
+ return mVolumeInfo.isMountedReadable();
+ }
+
+ public boolean isMountedWritable() {
+ return mVolumeInfo.isMountedWritable();
+ }
+
+ public boolean isPrimary() {
+ return mVolumeInfo.isPrimary();
+ }
+
+ public boolean isVisible() {
+ return mVolumeInfo.isVisible();
+ }
+
+ public boolean isVisibleForUser(int userId) {
+ return mVolumeInfo.isVisibleForUser(userId);
+ }
+
+ public boolean isVisibleForWrite(int userId) {
+ return mVolumeInfo.isVisibleForWrite(userId);
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b17eef85f93d..d84016b3816e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -246,9 +246,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WIND
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.sEnableShellTransitions;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -764,13 +762,6 @@ final class ActivityRecord extends WindowToken {
boolean mLastImeShown;
/**
- * When set to true, the IME insets will be frozen until the next app becomes IME input target.
- * @see InsetsPolicy#adjustVisibilityForIme
- * @see ImeInsetsSourceProvider#updateClientVisibility
- */
- boolean mImeInsetsFrozenUntilStartInput;
-
- /**
* A flag to determine if this AR is in the process of closing or entering PIP. This is needed
* to help AR know that the app is in the process of closing but hasn't yet started closing on
* the WM side.
@@ -1175,8 +1166,6 @@ final class ActivityRecord extends WindowToken {
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("mActivityType=");
pw.println(activityTypeToString(getActivityType()));
- pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput=");
- pw.println(mImeInsetsFrozenUntilStartInput);
if (requestedVrComponent != null) {
pw.print(prefix);
pw.print("requestedVrComponent=");
@@ -5239,7 +5228,8 @@ final class ActivityRecord extends WindowToken {
pendingOptions.getWidth(), pendingOptions.getHeight());
options = AnimationOptions.makeScaleUpAnimOptions(
pendingOptions.getStartX(), pendingOptions.getStartY(),
- pendingOptions.getWidth(), pendingOptions.getHeight());
+ pendingOptions.getWidth(), pendingOptions.getHeight(),
+ pendingOptions.getOverrideTaskTransition());
if (intent.getSourceBounds() == null) {
intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
pendingOptions.getStartY(),
@@ -5771,19 +5761,16 @@ final class ActivityRecord extends WindowToken {
return;
}
- final int windowsCount = mChildren.size();
- // With Shell-Transition, the activity will running a transition when it is visible.
- // It won't be included when fromTransition is true means the call from finishTransition.
- final boolean runningAnimation = sEnableShellTransitions ? visible
- : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
- for (int i = 0; i < windowsCount; i++) {
- mChildren.get(i).onAppVisibilityChanged(visible, runningAnimation);
+ if (!visible) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).onAppCommitInvisible();
+ }
}
setVisible(visible);
setVisibleRequested(visible);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "commitVisibility: %s: visible=%b"
- + " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
- this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
+ + " visibleRequested=%b, inTransition=%b, caller=%s",
+ this, visible, mVisibleRequested, inTransition(),
Debug.getCallers(5));
if (visible) {
// If we are being set visible, and the starting window is not yet displayed,
@@ -5873,10 +5860,6 @@ final class ActivityRecord extends WindowToken {
}
final DisplayContent displayContent = getDisplayContent();
- if (!visible) {
- mImeInsetsFrozenUntilStartInput = true;
- }
-
if (!displayContent.mClosingApps.contains(this)
&& !displayContent.mOpeningApps.contains(this)
&& !fromTransition) {
@@ -6224,13 +6207,8 @@ final class ActivityRecord extends WindowToken {
return false;
}
- // Hide all activities on the presenting display so that malicious apps can't do tap
- // jacking (b/391466268).
- // For now, this should only be applied to external displays because presentations can only
- // be shown on them.
- // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
- // the presentation won't stop its controlling activity.
- if (enablePresentationForConnectedDisplays() && mDisplayContent.mIsPresenting) {
+ // A presentation stopps all activities behind on the same display.
+ if (mWmService.mPresentationController.shouldOccludeActivities(getDisplayId())) {
return false;
}
@@ -6952,14 +6930,6 @@ final class ActivityRecord extends WindowToken {
// closing activity having to wait until idle timeout to be stopped or destroyed if the
// next activity won't report idle (e.g. repeated view animation).
mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
-
- // If the activity is visible, but no windows are eligible to start input, unfreeze
- // to avoid permanently frozen IME insets.
- if (mImeInsetsFrozenUntilStartInput && getWindow(
- win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags))
- == null) {
- mImeInsetsFrozenUntilStartInput = false;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ddb9f178cb8b..254127dee7a8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4324,10 +4324,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask(
t -> t.isActivityTypeStandard());
}
- if (task != null && task.getTopMostActivity() != null
- && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) {
+ final ActivityRecord topActivity = task != null
+ ? task.getTopMostActivity()
+ : null;
+ if (topActivity != null && !topActivity.isState(FINISHING, DESTROYING, DESTROYED)) {
mWindowManager.mAtmService.mActivityClientController
- .onPictureInPictureUiStateChanged(task.getTopMostActivity(), pipState);
+ .onPictureInPictureUiStateChanged(topActivity, pipState);
}
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 79bed3d8453d..e76a83453a9d 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -388,8 +388,7 @@ class BackNavigationController {
removedWindowContainer);
mBackAnimationInProgress = builder != null;
if (mBackAnimationInProgress) {
- if (removedWindowContainer.mTransitionController.inTransition()
- || mWindowManagerService.mSyncEngine.hasPendingSyncSets()) {
+ if (removedWindowContainer.mTransitionController.inTransition()) {
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
"Pending back animation due to another animation is running");
mPendingAnimationBuilder = builder;
@@ -817,6 +816,8 @@ class BackNavigationController {
if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
&& ar.mTransitionController.isCollecting(ar)) {
final TransitionController controller = ar.mTransitionController;
+ final Transition transition = controller.getCollectingTransition();
+ final int switchType = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType;
boolean collectTask = false;
ActivityRecord changedActivity = null;
for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
@@ -829,8 +830,16 @@ class BackNavigationController {
changedActivity = next;
}
}
- if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
- == AnimationHandler.TASK_SWITCH) {
+ if (Flags.unifyBackNavigationTransition()) {
+ for (int i = mAnimationHandler.mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
+ collectAnimatableTarget(transition, switchType,
+ mAnimationHandler.mOpenAnimAdaptor.mAdaptors[i].mTarget,
+ false /* isTop */);
+ }
+ collectAnimatableTarget(transition, switchType,
+ mAnimationHandler.mCloseAdaptor.mTarget, true /* isTop */);
+ }
+ if (collectTask && switchType == AnimationHandler.TASK_SWITCH) {
final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
if (topTask != null) {
WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
@@ -848,6 +857,18 @@ class BackNavigationController {
}
}
+ private static void collectAnimatableTarget(Transition transition, int switchType,
+ WindowContainer animatingTarget, boolean isTop) {
+ if ((switchType == AnimationHandler.ACTIVITY_SWITCH
+ && (animatingTarget.asActivityRecord() != null
+ || animatingTarget.asTaskFragment() != null))
+ || (switchType == AnimationHandler.TASK_SWITCH
+ && animatingTarget.asTask() != null)) {
+ transition.collect(animatingTarget);
+ transition.setBackGestureAnimation(animatingTarget, isTop);
+ }
+ }
+
// For shell transition
/**
* Check whether the transition targets was animated by back gesture animation.
@@ -992,8 +1013,8 @@ class BackNavigationController {
return;
}
- if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
- Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+ if (mWindowManagerService.mRoot.mTransitionController.inTransition()) {
+ Slog.v(TAG, "Skip predictive back transition, another transition is playing");
cancelPendingAnimation();
return;
}
@@ -1098,7 +1119,7 @@ class BackNavigationController {
}
final Transition prepareTransition = builder.prepareTransitionIfNeeded(
- openingActivities);
+ openingActivities, close, open);
final SurfaceControl.Transaction st = openingActivities[0].getSyncTransaction();
final SurfaceControl.Transaction ct = prepareTransition != null
? st : close.getPendingTransaction();
@@ -1790,7 +1811,8 @@ class BackNavigationController {
return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
- private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities) {
+ private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities,
+ WindowContainer promoteToClose, WindowContainer[] promoteToOpen) {
if (Flags.unifyBackNavigationTransition()) {
if (mCloseTarget.asWindowState() != null) {
return null;
@@ -1806,11 +1828,11 @@ class BackNavigationController {
final TransitionController tc = visibleOpenActivities[0].mTransitionController;
final Transition prepareOpen = tc.createTransition(
TRANSIT_PREPARE_BACK_NAVIGATION);
- tc.collect(mCloseTarget);
- prepareOpen.setBackGestureAnimation(mCloseTarget, true /* isTop */);
- for (int i = mOpenTargets.length - 1; i >= 0; --i) {
- tc.collect(mOpenTargets[i]);
- prepareOpen.setBackGestureAnimation(mOpenTargets[i], false /* isTop */);
+ tc.collect(promoteToClose);
+ prepareOpen.setBackGestureAnimation(promoteToClose, true /* isTop */);
+ for (int i = promoteToOpen.length - 1; i >= 0; --i) {
+ tc.collect(promoteToOpen[i]);
+ prepareOpen.setBackGestureAnimation(promoteToOpen[i], false /* isTop */);
}
if (!makeVisibles.isEmpty()) {
setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index a1faa7573a0c..f35930700653 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -51,8 +51,13 @@ public final class DesktopModeHelper {
}
/**
- * Return {@code true} if the current device supports desktop mode.
+ * Return {@code true} if the current device can hosts desktop sessions on its internal display.
*/
+ @VisibleForTesting
+ static boolean canInternalDisplayHostDesktops(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops);
+ }
+
// TODO(b/337819319): use a companion object instead.
private static boolean isDesktopModeSupported(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
@@ -67,12 +72,12 @@ public final class DesktopModeHelper {
*/
private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported(
- context) || isDeviceEligibleForDesktopMode(context));
+ context) || isInternalDisplayEligibleToHostDesktops(context));
}
@VisibleForTesting
- static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
- return !shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ static boolean isInternalDisplayEligibleToHostDesktops(@NonNull Context context) {
+ return !shouldEnforceDeviceRestrictions() || canInternalDisplayHostDesktops(context) || (
Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(
context));
}
@@ -81,12 +86,14 @@ public final class DesktopModeHelper {
* Return {@code true} if desktop mode can be entered on the current device.
*/
static boolean canEnterDesktopMode(@NonNull Context context) {
- return (isDesktopModeEnabled() && isDeviceEligibleForDesktopMode(context))
+ return (isInternalDisplayEligibleToHostDesktops(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()
+ && (isDesktopModeSupported(context) || !shouldEnforceDeviceRestrictions()))
|| isDesktopModeEnabledByDevOption(context);
}
/** Returns {@code true} if desktop experience wallpaper is supported on this device. */
public static boolean isDeviceEligibleForDesktopExperienceWallpaper(@NonNull Context context) {
- return enableConnectedDisplaysWallpaper() && isDeviceEligibleForDesktopMode(context);
+ return enableConnectedDisplaysWallpaper() && canEnterDesktopMode(context);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1dd7c4d4adbd..c87087f84399 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -547,9 +547,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
- /** Indicates whether any presentation is shown on this display. */
- boolean mIsPresenting;
-
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Region mTmpRegion = new Region();
@@ -4661,35 +4658,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- /**
- * Callback from {@link ImeInsetsSourceProvider#updateClientVisibility} for the system to
- * judge whether or not to notify the IME insets provider to dispatch this reported IME client
- * visibility state to the app clients when needed.
- */
- boolean onImeInsetsClientVisibilityUpdate() {
- boolean[] changed = new boolean[1];
-
- // Unlike the IME layering target or the control target can be updated during the layout
- // change, the IME input target requires to be changed after gaining the input focus.
- // In case unfreezing IME insets state may too early during IME focus switching, we unfreeze
- // when activities going to be visible until the input target changed, or the
- // activity was the current input target that has to unfreeze after updating the IME
- // client visibility.
- final ActivityRecord inputTargetActivity =
- mImeInputTarget != null ? mImeInputTarget.getActivityRecord() : null;
- final boolean targetChanged = mImeInputTarget != mLastImeInputTarget;
- if (targetChanged || inputTargetActivity != null && inputTargetActivity.isVisibleRequested()
- && inputTargetActivity.mImeInsetsFrozenUntilStartInput) {
- forAllActivities(r -> {
- if (r.mImeInsetsFrozenUntilStartInput && r.isVisibleRequested()) {
- r.mImeInsetsFrozenUntilStartInput = false;
- changed[0] = true;
- }
- });
- }
- return changed[0];
- }
-
void updateImeControlTarget() {
updateImeControlTarget(false /* forceUpdateImeParent */);
}
@@ -7141,14 +7109,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
+ * @return an integer as the changed requested visible insets types.
* @see #getRequestedVisibleTypes()
*/
- void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
- int newRequestedVisibleTypes =
+ @InsetsType int updateRequestedVisibleTypes(
+ @InsetsType int visibleTypes, @InsetsType int mask) {
+ final int newRequestedVisibleTypes =
(mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ newRequestedVisibleTypes;
mRequestedVisibleTypes = newRequestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 907d0dc2e183..7b6fc9e5694d 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -34,6 +34,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.InputTransferToken;
import com.android.internal.protolog.ProtoLog;
@@ -260,7 +261,7 @@ class EmbeddedWindowController {
// The EmbeddedWindow can only request the IME. All other insets types are requested by
// the host window.
- private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0;
+ private @InsetsType int mRequestedVisibleTypes = 0;
/** Whether the gesture is transferred to embedded window. */
boolean mGestureToEmbedded = false;
@@ -354,24 +355,28 @@ class EmbeddedWindowController {
}
@Override
- public boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types) {
+ public boolean isRequestedVisible(@InsetsType int types) {
return (mRequestedVisibleTypes & types) != 0;
}
@Override
- public @WindowInsets.Type.InsetsType int getRequestedVisibleTypes() {
+ public @InsetsType int getRequestedVisibleTypes() {
return mRequestedVisibleTypes;
}
/**
* Only the IME can be requested from the EmbeddedWindow.
- * @param requestedVisibleTypes other types than {@link WindowInsets.Type.IME} are
+ * @param requestedVisibleTypes other types than {@link WindowInsets.Type#ime()} are
* not sent to system server via WindowlessWindowManager.
+ * @return an integer as the changed requested visible insets types.
*/
- void setRequestedVisibleTypes(@WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ @InsetsType int setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ requestedVisibleTypes;
mRequestedVisibleTypes = requestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index cf16204f93a1..f52446ff494c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -282,7 +282,14 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// TODO(b/353463205) investigate if we should fail the statsToken, or if it's only
// temporary null.
if (target != null) {
- invokeOnImeRequestedChangedListener(target.getWindow(), statsToken);
+ // If insets target is not available (e.g. RemoteInsetsControlTarget), use current
+ // IME input target to update IME request state. For example, switch from a task
+ // with showing IME to a split-screen task without showing IME.
+ InsetsTarget insetsTarget = target.getWindow();
+ if (insetsTarget == null && mServerVisible) {
+ insetsTarget = mDisplayContent.getImeInputTarget();
+ }
+ invokeOnImeRequestedChangedListener(insetsTarget, statsToken);
}
}
}
@@ -314,7 +321,6 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
reportImeDrawnForOrganizerIfNeeded((InsetsControlTarget) caller);
}
}
- changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
if (Flags.refactorInsetsController()) {
if (changed) {
ImeTracker.forLogging().onProgress(statsToken,
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 4bcba13448e9..b4d55a160631 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -387,22 +387,6 @@ class InsetsPolicy {
state.addSource(navSource);
}
return state;
- } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
- // During switching tasks with gestural navigation, before the next IME input target
- // starts the input, we should adjust and freeze the last IME visibility of the window
- // in case delivering obsoleted IME insets state during transitioning.
- final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
-
- if (originalImeSource != null) {
- final boolean imeVisibility = w.isRequestedVisible(Type.ime());
- final InsetsState state = copyState
- ? new InsetsState(originalState)
- : originalState;
- final InsetsSource imeSource = new InsetsSource(originalImeSource);
- imeSource.setVisible(imeVisibility);
- state.addSource(imeSource);
- return state;
- }
} else if (w.mImeInsetsConsumed) {
// Set the IME source (if there is one) to be invisible if it has been consumed.
final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
@@ -453,9 +437,9 @@ class InsetsPolicy {
return originalState;
}
- void onRequestedVisibleTypesChanged(InsetsTarget caller,
+ void onRequestedVisibleTypesChanged(InsetsTarget caller, @InsetsType int changedTypes,
@Nullable ImeTracker.Token statsToken) {
- mStateController.onRequestedVisibleTypesChanged(caller, statsToken);
+ mStateController.onRequestedVisibleTypesChanged(caller, changedTypes, statsToken);
checkAbortTransient(caller);
updateBarControlTarget(mFocusedWin);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9202cf2d5792..164abab992d8 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -219,14 +219,20 @@ class InsetsStateController {
}
}
- void onRequestedVisibleTypesChanged(InsetsTarget caller,
+ void onRequestedVisibleTypesChanged(InsetsTarget caller, @InsetsType int changedTypes,
@Nullable ImeTracker.Token statsToken) {
boolean changed = false;
for (int i = mProviders.size() - 1; i >= 0; i--) {
final InsetsSourceProvider provider = mProviders.valueAt(i);
- final boolean isImeProvider = provider.getSource().getType() == WindowInsets.Type.ime();
- changed |= provider.updateClientVisibility(caller,
- isImeProvider ? statsToken : null);
+ final @InsetsType int type = provider.getSource().getType();
+ if ((type & changedTypes) != 0) {
+ final boolean isImeProvider = type == WindowInsets.Type.ime();
+ changed |= provider.updateClientVisibility(
+ caller, isImeProvider ? statsToken : null)
+ // Fake control target cannot change the client visibility, but it should
+ // change the insets with its newly requested visibility.
+ || (caller == provider.getFakeControlTarget());
+ }
}
if (changed) {
notifyInsetsChanged();
@@ -435,7 +441,8 @@ class InsetsStateController {
for (int i = newControlTargets.size() - 1; i >= 0; i--) {
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
// IME provider. Check if we have to create a new request here
- onRequestedVisibleTypesChanged(newControlTargets.valueAt(i), null /* statsToken */);
+ onRequestedVisibleTypesChanged(newControlTargets.valueAt(i),
+ WindowInsets.Type.all(), null /* statsToken */);
}
newControlTargets.clear();
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
new file mode 100644
index 000000000000..69463433827f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
+
+import android.annotation.NonNull;
+import android.util.IntArray;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
+
+/**
+ * Manages presentation windows.
+ */
+class PresentationController {
+
+ // TODO(b/395475549): Add support for display add/remove, and activity move across displays.
+ private final IntArray mPresentingDisplayIds = new IntArray();
+
+ PresentationController() {}
+
+ private boolean isPresenting(int displayId) {
+ return mPresentingDisplayIds.contains(displayId);
+ }
+
+ boolean shouldOccludeActivities(int displayId) {
+ // All activities on the presenting display must be hidden so that malicious apps can't do
+ // tap jacking (b/391466268).
+ // For now, this should only be applied to external displays because presentations can only
+ // be shown on them.
+ // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that
+ // the presentation won't stop its controlling activity.
+ return enablePresentationForConnectedDisplays() && isPresenting(displayId);
+ }
+
+ void onPresentationAdded(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
+ win.getDisplayId(), win);
+ mPresentingDisplayIds.add(win.getDisplayId());
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
+ }
+
+ void onPresentationRemoved(@NonNull WindowState win) {
+ final int displayId = win.getDisplayId();
+ if (!isPresenting(displayId)) {
+ return;
+ }
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION,
+ "Presentation removed from display %d: %s", win.getDisplayId(), win);
+ // TODO(b/393945496): Make sure that there's one presentation at most per display.
+ final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId);
+ if (displayIdIndex != -1) {
+ mPresentingDisplayIds.remove(displayIdIndex);
+ }
+ if (enablePresentationForConnectedDisplays()) {
+ // A presentation hides all activities behind on the same display.
+ win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
+ /*notifyClients=*/ true);
+ }
+ win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1ad5988e3c2e..8d198b26f396 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -704,9 +704,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
}
- win.setRequestedVisibleTypes(requestedVisibleTypes);
+ final @InsetsType int changedTypes =
+ win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
- imeStatsToken);
+ changedTypes, imeStatsToken);
final Task task = win.getTask();
if (task != null) {
task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
@@ -723,10 +724,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
// TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
- embeddedWindow.setRequestedVisibleTypes(
+ final @InsetsType int changedTypes = embeddedWindow.setRequestedVisibleTypes(
requestedVisibleTypes & WindowInsets.Type.ime());
embeddedWindow.getDisplayContent().getInsetsPolicy()
- .onRequestedVisibleTypesChanged(embeddedWindow, imeStatsToken);
+ .onRequestedVisibleTypesChanged(
+ embeddedWindow, changedTypes, imeStatsToken);
} else {
ImeTracker.forLogging().onFailed(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index cc14383fc9f9..ae3a015a690d 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -460,7 +460,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// If the previous front-most task is moved to the back, then notify of the new
// front-most task.
- final ActivityRecord topMost = getTopMostActivity();
+ final ActivityRecord topMost = getTopNonFinishingActivity();
if (topMost != null) {
mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(
topMost.getTask().getTaskInfo());
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 64105f634f84..324852d1a410 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1224,6 +1224,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
false /* ignoringKeyguard */, true /* ignoringInvisibleActivity */);
}
+ @Override
ActivityRecord getTopNonFinishingActivity() {
return getTopNonFinishingActivity(
true /* includeOverlays */, true /* includeLaunchedFromBubble */);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 563bcb771212..25b513d85384 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -555,6 +555,23 @@ class TransitionController {
return null;
}
+ /**
+ * @return The playing transition that is transiently-hiding the given {@param container}, or
+ * null if there isn't one
+ * @param container A participant of a transient-hide transition
+ */
+ @Nullable
+ Transition getTransientHideTransitionForContainer(
+ @NonNull WindowContainer container) {
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ final Transition transition = mPlayingTransitions.get(i);
+ if (transition.isInTransientHide(container)) {
+ return transition;
+ }
+ }
+ return null;
+ }
+
/** Returns {@code true} if the display contains a transient-launch transition. */
boolean hasTransientLaunch(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 225951dbd345..55c2668f62d0 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2079,6 +2079,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getActivity(alwaysTruePredicate(), true /* traverseTopToBottom */);
}
+ ActivityRecord getTopNonFinishingActivity() {
+ return getActivity(r -> !r.finishing, true /* traverseTopToBottom */);
+ }
+
ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
// Break down into 4 calls to avoid object creation due to capturing input params.
if (includeFinishing) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d5626661725e..bb669915e366 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,7 +157,6 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -503,6 +502,8 @@ public class WindowManagerService extends IWindowManager.Stub
final StartingSurfaceController mStartingSurfaceController;
+ final PresentationController mPresentationController;
+
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -1433,6 +1434,7 @@ public class WindowManagerService extends IWindowManager.Stub
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
+ mPresentationController = new PresentationController();
mBlurController = new BlurController(mContext, mPowerManager);
mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
@@ -1937,16 +1939,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
outSizeCompatScale[0] = win.getCompatScaleForClient();
- if (res >= ADD_OKAY
- && (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION)) {
- displayContent.mIsPresenting = true;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- displayContent.ensureActivitiesVisible(/*starting=*/ null,
- /*notifyClients=*/ true);
- }
- mDisplayManagerInternal.onPresentation(displayContent.getDisplay().getDisplayId(),
- /*isShown=*/ true);
+ if (res >= ADD_OKAY && win.isPresentation()) {
+ mPresentationController.onPresentationAdded(win);
}
}
@@ -4732,11 +4726,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
- dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
+ final @InsetsType int changedTypes =
+ dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(
+ visibleTypes, mask);
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
// IME provider. Check if we have to create a new request here, if null.
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
- dc.mRemoteInsetsControlTarget, statsToken);
+ dc.mRemoteInsetsControlTarget, changedTypes, statsToken);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a11f4b1f3fc3..924b9de5a562 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -702,9 +702,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if ((entry.getValue().getChangeMask()
& WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
- // Disable entering pip (eg. when recents pretends to finish itself)
- if (chain.mTransition != null) {
- chain.mTransition.setCanPipOnFinish(false /* canPipOnFinish */);
+ if (com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
+ // If we are using a bookend transition, then the transition that we need
+ // to disable pip on finish is the original transient transition, not the
+ // bookend transition
+ final Transition transientHideTransition =
+ mTransitionController.getTransientHideTransitionForContainer(wc);
+ if (transientHideTransition != null) {
+ transientHideTransition.setCanPipOnFinish(false);
+ } else {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Set do-not-pip: no task");
+ }
+ } else {
+ // Disable entering pip (eg. when recents pretends to finish itself)
+ if (chain.mTransition != null) {
+ chain.mTransition.setCanPipOnFinish(false /* canPipOnFinish */);
+ }
}
}
// A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 84d8f840d849..589724182980 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -182,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -822,17 +821,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
+ * @return an integer as the changed requested visible insets types.
* @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ @InsetsType int setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ final int changedTypes = mRequestedVisibleTypes ^ requestedVisibleTypes;
mRequestedVisibleTypes = requestedVisibleTypes;
+ return changedTypes;
}
+ return 0;
}
@VisibleForTesting
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes, @InsetsType int mask) {
- setRequestedVisibleTypes(mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
+ @InsetsType int setRequestedVisibleTypes(
+ @InsetsType int requestedVisibleTypes, @InsetsType int mask) {
+ return setRequestedVisibleTypes(
+ mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
}
/**
@@ -2069,38 +2074,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
super.onMovedByResize();
}
- void onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
+ void onAppCommitInvisible() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).onAppVisibilityChanged(visible, runningAppAnimation);
+ mChildren.get(i).onAppCommitInvisible();
}
-
- final boolean isVisibleNow = isVisibleNow();
- if (mAttrs.type == TYPE_APPLICATION_STARTING) {
- // Starting window that's exiting will be removed when the animation finishes.
- // Mark all relevant flags for that onExitAnimationDone will proceed all the way
- // to actually remove it.
- if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) {
- ProtoLog.d(WM_DEBUG_ANIM,
- "Set animatingExit: reason=onAppVisibilityChanged win=%s", this);
- mAnimatingExit = true;
- mRemoveOnExit = true;
- mWindowRemovalAllowed = true;
- }
- } else if (visible != isVisibleNow) {
- // Run exit animation if:
- // 1. App visibility and WS visibility are different
- // 2. App is not running an animation
- // 3. WS is currently visible
- if (!runningAppAnimation && isVisibleNow) {
- final AccessibilityController accessibilityController =
- mWmService.mAccessibilityController;
- final int winTransit = TRANSIT_EXIT;
- mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
- if (accessibilityController.hasCallbacks()) {
- accessibilityController.onWindowTransition(this, winTransit);
- }
- }
- setDisplayLayoutNeeded();
+ if (mAttrs.type != TYPE_APPLICATION_STARTING
+ && mWmService.mAccessibilityController.hasCallbacks()
+ // It is a change only if App visibility and WS visibility are different.
+ && isVisible()) {
+ mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT);
}
}
@@ -2317,15 +2299,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int type = mAttrs.type;
- if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
- // TODO(b/393945496): Make sure that there's one presentation at most per display.
- dc.mIsPresenting = false;
- if (enablePresentationForConnectedDisplays()) {
- // A presentation hides all activities behind on the same display.
- dc.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true);
- }
- mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
- /*isShown=*/ false);
+ if (isPresentation()) {
+ mWmService.mPresentationController.onPresentationRemoved(this);
}
// Check if window provides non decor insets before clearing its provided insets.
final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();
@@ -3354,6 +3329,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ boolean isPresentation() {
+ return mAttrs.type == TYPE_PRESENTATION || mAttrs.type == TYPE_PRIVATE_PRESENTATION;
+ }
+
private boolean isOnVirtualDisplay() {
return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index f154dbcee21a..09ce263e9b2f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3962,7 +3962,7 @@ public class DisplayModeDirectorTest {
}
@Override
- public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ public VotesStatsReporter getVotesStatsReporter() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java
index 2e4b97ef7dd2..371b0c926039 100644
--- a/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
+import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.multiuser.Flags;
import android.os.UserManager;
@@ -75,6 +76,8 @@ public class StorageManagerServiceTest {
@Before
public void setFixtures() {
+ PropertyInvalidatedCache.disableForTestMode();
+
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
index c824c3948e2d..c7c23f081044 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
@@ -1,3 +1,6 @@
-# Bug component: 44215
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 44215.
include /core/java/android/view/accessibility/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OWNERS
new file mode 100644
index 000000000000..9592bfdfa73b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1530954
+#
+# The above component is for automated test bugs. If you are a human looking to report
+# a bug in this codebase then please use component 770744.
+
+include /services/accessibility/java/com/android/server/accessibility/magnification/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index 770712a191fd..41011928f8b3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -204,7 +204,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase {
.build());
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
- .setCategory(CATEGORY_ALARM)
+ .setCategory(new String("alarm"))
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.build();
NotificationRecord r = getRecord(channel, n);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 65150e7b48fc..440f43e9b926 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -49,17 +49,13 @@ import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsSource.ID_IME;
-import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -125,7 +121,6 @@ import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
-import android.app.servertransaction.WindowStateResizeItem;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Intent;
@@ -149,8 +144,6 @@ import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner.Stub;
import android.view.IWindowManager;
-import android.view.InsetsSource;
-import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -171,7 +164,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
@@ -3370,178 +3362,6 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
- public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
- final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
- makeWindowVisibleAndDrawn(app, mImeWindow);
- mDisplayContent.setImeLayeringTarget(app);
- mDisplayContent.setImeInputTarget(app);
-
- // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
- mDisplayContent.mOpeningApps.clear();
- app.mActivityRecord.commitVisibility(false, false);
- app.mActivityRecord.onWindowsGone();
-
- assertTrue(app.mActivityRecord.mLastImeShown);
- assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
-
- // Expect IME insets frozen state will reset when the activity has no IME focusable window.
- app.mActivityRecord.forAllWindows(w -> {
- w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
- return true;
- }, true);
-
- app.mActivityRecord.commitVisibility(true, false);
- app.mActivityRecord.onWindowsVisible();
-
- assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
- }
-
- @SetupWindows(addWindows = W_INPUT_METHOD)
- @Test
- public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
- final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
-
- mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
- mImeWindow, null, null);
- mImeWindow.getControllableInsetProvider().setServerVisible(true);
-
- InsetsSource imeSource = new InsetsSource(ID_IME, ime());
- app.mAboveInsetsState.addSource(imeSource);
- mDisplayContent.setImeLayeringTarget(app);
- mDisplayContent.updateImeInputAndControlTarget(app);
-
- InsetsState state = app.getInsetsState();
- assertFalse(state.getOrCreateSource(imeSource.getId(), ime()).isVisible());
- assertTrue(state.getOrCreateSource(imeSource.getId(), ime()).getFrame().isEmpty());
-
- // Simulate app is closing and expect IME insets is frozen.
- mDisplayContent.mOpeningApps.clear();
- app.mActivityRecord.commitVisibility(false, false);
- app.mActivityRecord.onWindowsGone();
- assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
-
- // Simulate app re-start input or turning screen off/on then unlocked by un-secure
- // keyguard to back to the app, expect IME insets is not frozen
- app.mActivityRecord.commitVisibility(true, false);
- mDisplayContent.updateImeInputAndControlTarget(app);
- performSurfacePlacementAndWaitForWindowAnimator();
-
- assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
-
- imeSource.setVisible(true);
- imeSource.setFrame(new Rect(100, 400, 500, 500));
- app.mAboveInsetsState.addSource(imeSource);
-
- // Verify when IME is visible and the app can receive the right IME insets from policy.
- makeWindowVisibleAndDrawn(app, mImeWindow);
- state = app.getInsetsState();
- assertTrue(state.peekSource(ID_IME).isVisible());
- assertEquals(state.peekSource(ID_IME).getFrame(), imeSource.getFrame());
- }
-
- @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
- @Test
- public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
- throws RemoteException {
- final WindowState app1 = newWindowBuilder("app1", TYPE_APPLICATION).build();
- final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).build();
-
- mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
- mImeWindow, null, null);
- mImeWindow.getControllableInsetProvider().setServerVisible(true);
-
- // Simulate app2 is closing and let app1 is visible to be IME targets.
- makeWindowVisibleAndDrawn(app1, mImeWindow);
- mDisplayContent.setImeLayeringTarget(app1);
- mDisplayContent.updateImeInputAndControlTarget(app1);
- app2.mActivityRecord.commitVisibility(false, false);
-
- // app1 requests IME visible.
- app1.setRequestedVisibleTypes(ime(), ime());
- mDisplayContent.getInsetsStateController().onRequestedVisibleTypesChanged(app1,
- null /* statsToken */);
-
- // Verify app1's IME insets is visible and app2's IME insets frozen flag set.
- assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
- assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
-
- // Simulate switching to app2 to make it visible to be IME targets.
- spyOn(app2);
- spyOn(app2.mClient);
- spyOn(app2.getProcess());
- ArgumentCaptor<InsetsState> insetsStateCaptor = ArgumentCaptor.forClass(InsetsState.class);
- doReturn(true).when(app2).isReadyToDispatchInsetsState();
- mDisplayContent.setImeLayeringTarget(app2);
- app2.mActivityRecord.commitVisibility(true, false);
- mDisplayContent.updateImeInputAndControlTarget(app2);
- performSurfacePlacementAndWaitForWindowAnimator();
-
- // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets
- // to client if the app didn't request IME visible.
- assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
-
- verify(app2.getProcess(), atLeastOnce()).scheduleClientTransactionItem(
- isA(WindowStateResizeItem.class));
- assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
- }
-
- @Test
- public void testImeInsetsFrozenFlag_multiWindowActivities() {
- final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent);
- final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).setWindowToken(
- imeToken).build();
- makeWindowVisibleAndDrawn(ime);
-
- // Create a split-screen root task with activity1 and activity 2.
- final Task task = new TaskBuilder(mSupervisor)
- .setCreateParentTask(true).setCreateActivity(true).build();
- task.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- final ActivityRecord activity1 = task.getTopNonFinishingActivity();
- activity1.getTask().setResumedActivity(activity1, "testApp1");
-
- final ActivityRecord activity2 = new TaskBuilder(mSupervisor)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .setCreateActivity(true).build().getTopMostActivity();
- activity2.getTask().setResumedActivity(activity2, "testApp2");
- activity2.getTask().setParent(task.getRootTask());
-
- // Simulate activity1 and activity2 both have set mImeInsetsFrozenUntilStartInput when
- // invisible to user.
- activity1.mImeInsetsFrozenUntilStartInput = true;
- activity2.mImeInsetsFrozenUntilStartInput = true;
-
- final WindowState app1 = newWindowBuilder("app1", TYPE_APPLICATION).setWindowToken(
- activity1).build();
- final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).setWindowToken(
- activity2).build();
- makeWindowVisibleAndDrawn(app1, app2);
-
- final InsetsStateController controller = mDisplayContent.getInsetsStateController();
- controller.getImeSourceProvider().setWindowContainer(
- ime, null, null);
- ime.getControllableInsetProvider().setServerVisible(true);
-
- // app1 starts input and expect IME insets for all activities in split-screen will be
- // frozen until the input started.
- mDisplayContent.setImeLayeringTarget(app1);
- mDisplayContent.updateImeInputAndControlTarget(app1);
- mDisplayContent.computeImeTarget(true /* updateImeTarget */);
- performSurfacePlacementAndWaitForWindowAnimator();
-
- assertEquals(app1, mDisplayContent.getImeInputTarget());
- assertFalse(activity1.mImeInsetsFrozenUntilStartInput);
- assertFalse(activity2.mImeInsetsFrozenUntilStartInput);
-
- app1.setRequestedVisibleTypes(ime());
- controller.onRequestedVisibleTypesChanged(app1, null /* statsToken */);
-
- // Expect all activities in split-screen will get IME insets visible state
- assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
- assertTrue(app2.getInsetsState().peekSource(ID_IME).isVisible());
- }
-
@Test
public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
index e0b700a4ffe3..eaffc481098e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -97,6 +97,7 @@ public class DesktopModeHelperTest {
public void canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceCheck_returnsFalse()
throws Exception {
doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
doReturn(true).when(mMockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported));
disableEnforceDeviceRestriction();
@@ -148,6 +149,7 @@ public class DesktopModeHelperTest {
@Test
public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
}
@@ -176,21 +178,21 @@ public class DesktopModeHelperTest {
@Test
public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
- doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops));
- assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue();
}
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
- assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse();
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@Test
public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
- assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isFalse();
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@@ -200,7 +202,7 @@ public class DesktopModeHelperTest {
eq(R.bool.config_isDesktopModeDevOptionSupported)
);
- assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ assertThat(DesktopModeHelper.isInternalDisplayEligibleToHostDesktops(mMockContext)).isTrue();
}
private void resetEnforceDeviceRestriction() throws Exception {
@@ -234,4 +236,4 @@ public class DesktopModeHelperTest {
Settings.Global.putInt(mContext.getContentResolver(),
DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting());
}
-}
+} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index fdde3b38f19f..d305c2f54456 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -1345,7 +1345,7 @@ public class DesktopModeLaunchParamsModifierTests extends
private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported,
boolean enforceDeviceRestrictions) {
doReturn(isDesktopModeSupported)
- .when(() -> DesktopModeHelper.isDeviceEligibleForDesktopMode(any()));
+ .when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
doReturn(enforceDeviceRestrictions)
.when(DesktopModeHelper::shouldEnforceDeviceRestrictions);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
index 285a5e246e0c..ea21bb34597d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopWindowingRobot.java
@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any;
/** Robot for changing desktop windowing properties. */
class DesktopWindowingRobot {
void allowEnterDesktopMode(boolean isAllowed) {
- doReturn(isAllowed).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
+ doReturn(isAllowed).when(() ->
+ DesktopModeHelper.canEnterDesktopMode(any()));
}
}
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 6c5fe1d8551e..71e34ef220d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -53,6 +53,7 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.test.filters.SmallTest;
@@ -400,9 +401,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertTrue(state.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
assertTrue(state.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
- mAppWindow.setRequestedVisibleTypes(
+ final @InsetsType int changedTypes = mAppWindow.setRequestedVisibleTypes(
navigationBars() | statusBars(), navigationBars() | statusBars());
- policy.onRequestedVisibleTypesChanged(mAppWindow, null /* statsToken */);
+ policy.onRequestedVisibleTypesChanged(mAppWindow, changedTypes, null /* statsToken */);
waitUntilWindowAnimatorIdle();
controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
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 973c8d0a8464..5525bae89138 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -52,6 +52,7 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.test.filters.SmallTest;
@@ -201,8 +202,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getOrCreateSourceProvider(ID_IME, ime())
.setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(base);
- base.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+ final @InsetsType int changedTypes = base.setRequestedVisibleTypes(ime(), ime());
+ getController().onRequestedVisibleTypesChanged(base, changedTypes, null /* statsToken */);
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
@@ -509,8 +510,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.updateImeInputAndControlTarget(app);
- app.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
+ final @InsetsType int changedTypes = app.setRequestedVisibleTypes(ime(), ime());
+ getController().onRequestedVisibleTypesChanged(app, changedTypes, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
new file mode 100644
index 000000000000..db90c28ec7df
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.FLAG_PRESENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.graphics.Rect;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.view.DisplayInfo;
+import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:PresentationControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PresentationControllerTests extends WindowTestsBase {
+
+ @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testPresentationHidesActivitiesBehind() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ displayInfo.flags = FLAG_PRESENTATION;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+ final int displayId = dc.getDisplayId();
+ doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
+ final ActivityRecord activity = createActivityRecord(createTask(dc));
+ assertTrue(activity.isVisible());
+
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
+ final int uid = 100000; // uid for non-system user
+ final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+ final int userId = UserHandle.getUserId(uid);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_PRESENTATION);
+
+ final IWindow clientWindow = new TestIWindow();
+ final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
+ userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+ new InsetsSourceControl.Array(), new Rect(), new float[1]);
+ assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
+ assertFalse(activity.isVisible());
+
+ final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
+ window.removeImmediately();
+ assertTrue(activity.isVisible());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 1323d8a59cef..71e84c0f1821 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,6 @@ import static android.permission.flags.Flags.FLAG_SENSITIVE_CONTENT_RECENTS_SCRE
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
-import static android.view.Display.FLAG_PRESENTATION;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -55,7 +54,6 @@ import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -102,7 +100,6 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
-import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -1409,38 +1406,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
assertEquals(activityWindowInfo2, activityWindowInfo3);
}
- @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
- @Test
- public void testPresentationHidesActivitiesBehind() {
- DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.copyFrom(mDisplayInfo);
- displayInfo.flags = FLAG_PRESENTATION;
- DisplayContent dc = createNewDisplay(displayInfo);
- int displayId = dc.getDisplayId();
- doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
- ActivityRecord activity = createActivityRecord(createTask(dc));
- assertTrue(activity.isVisible());
-
- doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
- int uid = 100000; // uid for non-system user
- Session session = createTestSession(mAtm, 1234 /* pid */, uid);
- int userId = UserHandle.getUserId(uid);
- doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- LayoutParams.TYPE_PRESENTATION);
-
- final IWindow clientWindow = new TestIWindow();
- int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
- userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
- new InsetsSourceControl.Array(), new Rect(), new float[1]);
- assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
- assertFalse(activity.isVisible());
-
- final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
- window.removeImmediately();
- assertTrue(activity.isVisible());
- }
-
@Test
public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index cff172f55601..a718c06cc2fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1282,7 +1282,6 @@ public class WindowStateTests extends WindowTestsBase {
// Simulate app plays closing transition to app2.
app.mActivityRecord.commitVisibility(false, false);
assertTrue(app.mActivityRecord.mLastImeShown);
- assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
@@ -1305,7 +1304,7 @@ public class WindowStateTests extends WindowTestsBase {
// Simulate app2 in multi-window mode is going to background to switch to the fullscreen
// app which requests IME with updating all windows Insets State when IME is above app.
- app2.mActivityRecord.mImeInsetsFrozenUntilStartInput = true;
+ app2.mActivityRecord.setVisibleRequested(false);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
app.setRequestedVisibleTypes(ime(), ime());
@@ -1324,7 +1323,6 @@ public class WindowStateTests extends WindowTestsBase {
mDisplayContent.setImeLayeringTarget(app2);
app.mActivityRecord.commitVisibility(false, false);
assertTrue(app.mActivityRecord.mLastImeShown);
- assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is still visible on app, but not for app2 during task switching.
assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e0af22369182..d2741ac7ee9f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4821,10 +4821,14 @@ public class SubscriptionManager {
+ "Invalid subscriptionId: " + subscriptionId);
}
+ String contextPkg = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ String contextAttributionTag = mContext != null ? mContext.getAttributionTag() : null;
+
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- return iSub.isSubscriptionAssociatedWithCallingUser(subscriptionId);
+ return iSub.isSubscriptionAssociatedWithCallingUser(subscriptionId, contextPkg,
+ contextAttributionTag);
} else {
throw new IllegalStateException("subscription service unavailable.");
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 1bfec29a3cf4..a974c615a4ae 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -347,13 +347,17 @@ interface ISub {
* Returns whether the given subscription is associated with the calling user.
*
* @param subscriptionId the subscription ID of the subscription
+ * @param callingPackage The package maing the call
+ * @param callingFeatureId The feature in the package
+
* @return {@code true} if the subscription is associated with the user that the current process
* is running in; {@code false} otherwise.
*
* @throws IllegalArgumentException if subscription doesn't exist.
* @throws SecurityException if the caller doesn't have permissions required.
*/
- boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId);
+ boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId, String callingPackage,
+ String callingFeatureId);
/**
* Check if subscription and user are associated with each other.
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 12670cda74b2..ac704e5e7c39 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -52,10 +52,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 481a8bb66fee..1b2007deae27 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -50,10 +50,12 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
+ <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/>
<option name="teardown-command"
value="settings delete secure show_ime_with_hard_keyboard"/>
<option name="teardown-command" value="settings delete system show_touches"/>
<option name="teardown-command" value="settings delete system pointer_location"/>
+ <option name="teardown-command" value="settings delete secure glanceable_hub_enabled"/>
<option name="teardown-command"
value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
</target_preparer>
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index f00a6cad6b46..20315561cceb 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -54,9 +54,7 @@ std::string GetSafePath(StringPiece arg) {
void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- if (value) {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
- }
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
@@ -67,9 +65,7 @@ void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::st
void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- if (value) {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
- }
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
@@ -80,9 +76,7 @@ void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalFlag(StringPiece name, StringPiece description,
std::optional<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- if (value) {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
- }
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
@@ -93,9 +87,7 @@ void Command::AddOptionalFlag(StringPiece name, StringPiece description,
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
- if (value) {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
- }
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
@@ -106,9 +98,7 @@ void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::unordered_set<std::string>* value) {
auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
- if (value) {
- value->emplace(arg);
- }
+ value->emplace(arg);
return true;
};
@@ -118,9 +108,7 @@ void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
- if (value) {
- *value = true;
- }
+ *value = true;
return true;
};
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index ad167c979662..2a3cb2a0c65d 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -159,22 +159,4 @@ TEST(CommandTest, ShortOptions) {
ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr));
}
-TEST(CommandTest, OptionsWithNullptrToAcceptValues) {
- TestCommand command;
- command.AddRequiredFlag("--rflag", "", nullptr);
- command.AddRequiredFlagList("--rlflag", "", nullptr);
- command.AddOptionalFlag("--oflag", "", nullptr);
- command.AddOptionalFlagList("--olflag", "", (std::vector<std::string>*)nullptr);
- command.AddOptionalFlagList("--olflag2", "", (std::unordered_set<std::string>*)nullptr);
- command.AddOptionalSwitch("--switch", "", nullptr);
-
- ASSERT_EQ(0, command.Execute({
- "--rflag"s, "1"s,
- "--rlflag"s, "1"s,
- "--oflag"s, "1"s,
- "--olflag"s, "1"s,
- "--olflag2"s, "1"s,
- "--switch"s}, &std::cerr));
-}
-
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 060bc5fa2242..6c3eae11eab9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -425,6 +425,9 @@ int ConvertCommand::Action(const std::vector<std::string>& args) {
<< output_format_.value());
return 1;
}
+ if (enable_sparse_encoding_) {
+ table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled;
+ }
if (force_sparse_encoding_) {
table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
}
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 98c8f5ff89c0..9452e588953e 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -36,9 +36,11 @@ class ConvertCommand : public Command {
kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_);
AddOptionalSwitch(
"--enable-sparse-encoding",
- "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
- "enabled if minSdk of the APK is >= 32.",
- nullptr);
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
+ "the APK is O+",
+ &enable_sparse_encoding_);
AddOptionalSwitch("--force-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
@@ -85,6 +87,7 @@ class ConvertCommand : public Command {
std::string output_path_;
std::optional<std::string> output_format_;
bool verbose_ = false;
+ bool enable_sparse_encoding_ = false;
bool force_sparse_encoding_ = false;
bool enable_compact_entries_ = false;
std::optional<std::string> resources_config_path_;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4718fbf085f8..ff4d8ef2ec25 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -2505,6 +2505,9 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
<< "the --merge-only flag can be only used when building a static library");
return 1;
}
+ if (options_.use_sparse_encoding) {
+ options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
+ }
// The default build type.
context.SetPackageType(PackageType::kApp);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index b5bd905c02be..2f17853718ec 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -75,6 +75,7 @@ struct LinkOptions {
bool no_resource_removal = false;
bool no_xml_namespaces = false;
bool do_not_compress_anything = false;
+ bool use_sparse_encoding = false;
std::unordered_set<std::string> extensions_to_not_compress;
std::optional<std::regex> regex_to_not_compress;
FeatureFlagValues feature_flag_values;
@@ -162,11 +163,9 @@ class LinkCommand : public Command {
AddOptionalSwitch("--no-resource-removal", "Disables automatic removal of resources without\n"
"defaults. Use this only when building runtime resource overlay packages.",
&options_.no_resource_removal);
- AddOptionalSwitch(
- "--enable-sparse-encoding",
- "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
- "enabled if minSdk of the APK is >= 32.",
- nullptr);
+ AddOptionalSwitch("--enable-sparse-encoding",
+ "This decreases APK size at the cost of resource retrieval performance.",
+ &options_.use_sparse_encoding);
AddOptionalSwitch("--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
&options_.table_flattener_options.use_compact_entries);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index f218307af578..762441ee1872 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -406,6 +406,9 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) {
return 1;
}
+ if (options_.enable_sparse_encoding) {
+ options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled;
+ }
if (options_.force_sparse_encoding) {
options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced;
}
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index e3af584cbbd9..012b0f230ca2 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -61,6 +61,9 @@ struct OptimizeOptions {
// TODO(b/246489170): keep the old option and format until transform to the new one
std::optional<std::string> shortened_paths_map_path;
+ // Whether sparse encoding should be used for O+ resources.
+ bool enable_sparse_encoding = false;
+
// Whether sparse encoding should be used for all resources.
bool force_sparse_encoding = false;
@@ -103,9 +106,11 @@ class OptimizeCommand : public Command {
&kept_artifacts_);
AddOptionalSwitch(
"--enable-sparse-encoding",
- "[DEPRECATED] This flag is a no-op as of aapt2 v2.20. Sparse encoding is always\n"
- "enabled if minSdk of the APK is >= 32.",
- nullptr);
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
+ "the APK is O+",
+ &options_.enable_sparse_encoding);
AddOptionalSwitch("--force-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index b8ac7925d44e..1a82021bce71 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -201,7 +201,7 @@ class PackageFlattener {
(context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
// Sparse encode if forced or sdk version is not set in context and config.
} else {
- // Otherwise, only sparse encode if the entries will be read on platforms S_V2+ (32).
+ // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2);
}
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index f1c4c3512ed3..0633bc81cb25 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -37,7 +37,8 @@ constexpr const size_t kSparseEncodingThreshold = 60;
enum class SparseEntriesMode {
// Disables sparse encoding for entries.
Disabled,
- // Enables sparse encoding for all entries for APKs with minSdk >= 32 (S_V2).
+ // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less
+ // than O only applies sparse encoding for resource configuration available on O+.
Enabled,
// Enables sparse encoding for all entries regardless of minSdk.
Forced,
@@ -46,7 +47,7 @@ enum class SparseEntriesMode {
struct TableFlattenerOptions {
// When enabled, types for configurations with a sparse set of entries are encoded
// as a sparse map of entry ID and offset to actual data.
- SparseEntriesMode sparse_entries = SparseEntriesMode::Enabled;
+ SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
// When true, use compact entries for simple data
bool use_compact_entries = false;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index e3d589eb078b..0f1168514c4a 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -337,13 +337,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
- options.sparse_entries = SparseEntriesMode::Disabled;
+ options.sparse_entries = SparseEntriesMode::Enabled;
std::string no_sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &no_sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
@@ -421,13 +421,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
TableFlattenerOptions options;
- options.sparse_entries = SparseEntriesMode::Disabled;
+ options.sparse_entries = SparseEntriesMode::Enabled;
std::string no_sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &no_sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
std::string sparse_contents;
- ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &sparse_contents));
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 664d8412a3be..5c3dfdcadfec 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -3,8 +3,6 @@
## Version 2.20
- Too many features, bug fixes, and improvements to list since the last minor version update in
2017. This README will be updated more frequently in the future.
-- Sparse encoding is now always enabled by default if the minSdkVersion is >= 32 (S_V2). The
- `--enable-sparse-encoding` flag still exists, but is a no-op.
## Version 2.19
- Added navigation resource type.