summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/Notification.java15
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java7
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java448
-rw-r--r--core/java/android/ddm/DdmHandleHello.java25
-rw-r--r--core/java/android/os/Debug.java17
-rw-r--r--core/java/android/view/PointerIcon.java17
-rw-r--r--core/java/android/window/BackNavigationInfo.java27
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java43
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java221
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java2
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java12
-rw-r--r--core/java/com/android/internal/view/menu/StandardMenuPopup.java18
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/layout/list_menu_item_icon.xml2
-rw-r--r--core/res/res/layout/popup_menu_item_layout_material.xml91
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/Android.bp6
-rw-r--r--keystore/java/android/security/KeyStore.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java5
-rw-r--r--media/java/android/media/session/ParcelableListBinder.java13
-rw-r--r--packages/CredentialManager/wear/res/drawable/passkey_icon.xml21
-rw-r--r--packages/CredentialManager/wear/res/values-watch/donottranslate.xml31
-rw-r--r--packages/CredentialManager/wear/res/values/overlayable.xml39
-rw-r--r--packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt17
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt4
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt2
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt9
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt8
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt8
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt2
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt17
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt18
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt11
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt34
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt12
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt9
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialSelectorTheme.kt144
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialTonalPalette.kt205
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java53
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig17
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt55
-rw-r--r--packages/SystemUI/res/drawable/shelf_action_chip_background.xml27
-rw-r--r--packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/shelf_action_chip_divider.xml21
-rw-r--r--packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml (renamed from packages/CredentialManager/wear/res/values/colors.xml)12
-rw-r--r--packages/SystemUI/res/layout/bluetooth_tile_dialog.xml20
-rw-r--r--packages/SystemUI/res/layout/qs_panel.xml4
-rw-r--r--packages/SystemUI/res/layout/screenshot_shelf.xml54
-rw-r--r--packages/SystemUI/res/layout/shelf_action_chip.xml46
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt16
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/ailabs/OWNERS9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt166
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Util.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java155
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt69
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt3
-rw-r--r--proto/src/am_capabilities.proto10
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java7
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java26
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java37
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java27
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java23
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java2
-rw-r--r--services/core/java/com/android/server/am/PendingIntentController.java20
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java59
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java1
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java8
-rw-r--r--services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java16
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java77
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java86
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java8
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerPrivate.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java322
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java4
-rw-r--r--services/core/java/com/android/server/notification/ShortcutHelper.java7
-rw-r--r--services/core/java/com/android/server/notification/TimeToLiveHelper.java175
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java30
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java45
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java113
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java30
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java71
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java331
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java221
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java6
-rw-r--r--tests/OneMedia/Android.bp1
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java12
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java13
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java26
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java51
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java55
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java27
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java42
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java14
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java33
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java9
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java11
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java5
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java10
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java11
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java9
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java19
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java86
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java33
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java25
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java59
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java6
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java4
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java44
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java24
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java28
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java22
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java22
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java18
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java14
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/missing-url.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml1
202 files changed, 5081 insertions, 1497 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35ab5f014815..443a6c0e91e6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -4135,7 +4135,6 @@ package android.window {
method @NonNull public static String typeToString(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
- field public static final String KEY_TRIGGER_BACK = "TriggerBack";
field public static final int TYPE_CALLBACK = 4; // 0x4
field public static final int TYPE_CROSS_ACTIVITY = 2; // 0x2
field public static final int TYPE_CROSS_TASK = 3; // 0x3
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dbde7d20f0d8..6ff1bfc5291c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2583,6 +2583,7 @@ public class Notification implements Parcelable
this.when = System.currentTimeMillis();
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -2598,6 +2599,7 @@ public class Notification implements Parcelable
{
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
new Builder(context)
.setWhen(when)
@@ -2630,6 +2632,7 @@ public class Notification implements Parcelable
this.when = when;
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -4382,14 +4385,16 @@ public class Notification implements Parcelable
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
- * shown anymore by default and must be opted into by using
- * {@link android.app.Notification.Builder#setShowWhen(boolean)}
- *
* @see Notification#when
*/
@NonNull
public Builder setWhen(long when) {
+ if (updateRankingTime()) {
+ // don't show a timestamp that's decades old
+ if (mN.extras.getBoolean(EXTRA_SHOW_WHEN, true) && when == 0) {
+ return this;
+ }
+ }
mN.when = when;
return this;
}
@@ -4397,8 +4402,6 @@ public class Notification implements Parcelable
/**
* Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
* in the content view.
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
- * {@code false}. For earlier apps, the default is {@code true}.
*/
@NonNull
public Builder setShowWhen(boolean show) {
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index f21e11ab35cc..c7b0be7553c2 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -244,6 +244,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}. You will generally handle
* this in {@link DeviceAdminReceiver#onProfileProvisioningComplete}.
*
+ * <p>The intent for this action may include the following extras:
+ * <ul>
+ * <li>{@link DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}
+ *
* @see DevicePolicyManager#ACTION_PROVISIONING_SUCCESSFUL
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -801,6 +805,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* {@link DevicePolicyManager#ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the same
* application.
*
+ * <p>The {@code Intent} may include any of the extras specified for
+ * {@link #ACTION_PROFILE_PROVISIONING_COMPLETE}.
+ *
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ba91be9e9e6c..9058713ecfa1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -192,32 +192,134 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-// TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
/**
- * Public interface for managing policies enforced on a device. Most clients of this class must be
- * registered with the system as a <a href="{@docRoot}guide/topics/admin/device-admin.html">device
- * administrator</a>. Additionally, a device administrator may be registered as either a profile or
- * device owner. A given method is accessible to all device administrators unless the documentation
- * for that method specifies that it is restricted to either device or profile owners. Any
- * application calling an api may only pass as an argument a device administrator component it
- * owns. Otherwise, a {@link SecurityException} will be thrown.
+ * Manages device policy and restrictions applied to the user of the device or
+ * apps running on the device.
*
- * <p><b>Note: </b>on
- * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, some methods can
- * throw an {@link UnsafeStateException} exception (for example, if the vehicle is moving), so
- * callers running on automotive builds should always check for that exception, otherwise they
- * might crash.
+ * <p>This class contains three types of methods:
+ * <ol><li>Those aimed at <a href="#managingapps">managing apps</a>
+ * <li>Those aimed at the <a href="#roleholder">Device Policy Management Role Holder</a>
+ * <li>Those aimed at <a href="#querying">apps which wish to respect device policy</a>
+ * </ol>
*
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>
- * For more information about managing policies for device administration, read the <a href=
- * "{@docRoot}guide/topics/admin/device-admin.html">Device Administration</a> developer
- * guide. </div>
+ * <p>The intended caller for each API is indicated in its Javadoc.
+ *
+ * <p id="managingapps"><b>Managing Apps</b>
+ * <p>Apps can be made capable of setting device policy ("Managing Apps") either by
+ * being set as a <a href="#deviceadmin">Device Administrator</a>, being set as a
+ * <a href="#devicepolicycontroller">Device Policy Controller</a>, or by holding the
+ * appropriate <a href="#permissions">Permissions</a>.
+ *
+ * <p id="deviceadmin">A <b>Device Administrator</b> is an app which is able to enforce device
+ * policies that it has declared in its device admin XML file. An app can prompt the user to give it
+ * device administator privileges using the {@link #ACTION_ADD_DEVICE_ADMIN} action.
+ *
+ * <p>For more information about Device Administration, read the
+ * <a href="{@docRoot}guide/topics/admin/device-admin.html">Device Administration</a>
+ * developer guide.
+ *
+ * <p id="devicepolicycontroller">Through <a href="#managed_provisioning">Managed Provisioning</a>,
+ * Device Administrator apps can also be recognised as <b>
+ Device Policy Controllers</b>. Device Policy Controllers can be one of
+ * two types:
+ * <ul>
+ * <li>A <i id="deviceowner">Device Owner</i>, which only ever exists on the
+ * {@link UserManager#isSystemUser System User} or {@link UserManager#isMainUser Main User}, is
+ * the most powerful type of Device Policy Controller and can affect policy across the device.
+ * <li>A <i id="profileowner">Profile Owner<i>, which can exist on any user, can
+ * affect policy on the user it is on, and when it is running on
+ * {@link UserManager#isProfile a profile} has
+ * <a href="#profile-on-parent">limited</a> ability to affect policy on its
+ * {@link UserManager#getProfileParent parent}.
+ * </ul>
+ *
+ * <p>Additional capabilities can be provided to Device Policy Controllers in
+ * the following circumstances:
+ * <ul>
+ * <li>A Profile Owner on an <a href="#organization-owned">organization owned</a> device has access
+ * to additional abilities, both <a href="#profile-on-parent-organization-owned">affecting policy on the profile's</a>
+ * {@link UserManager#getProfileParent parent} and also the profile itself.
+ * <li>A Profile Owner running on the {@link UserManager#isSystemUser System User} has access to
+ * additional capabilities which affect the {@link UserManager#isSystemUser System User} and
+ * also the whole device.
+ * <li>A Profile Owner running on an <a href="#affiliated">affiliated</a> user has
+ * capabilities similar to that of a <a href="#deviceowner">Device Owner</a>
+ * </ul>
+ *
+ * <p>For more information, see <a href="{@docRoot}work/dpc/build-dpc">Building a Device Policy
+ * Controller</a>.
+ *
+ * <p><a href="#permissions">Permissions</a> are generally only given to apps
+ * fulfilling particular key roles on the device (such as managing {@link DeviceLockManager
+device locks}).
+ *
+ * <p id="roleholder"><b>Device Policy Management Role Holder</b>
+ * <p>One app on the device fulfills the {@link RoleManager#ROLE_DEVICE_POLICY_MANAGEMENT Device
+Policy Management Role} and is trusted with managing the overall state of
+ * Device Policy. This has access to much more powerful methods than
+ * <a href="#managingapps">managing apps</a>.
+ *
+ * <p id="querying"><b>Querying Device Policy</b>
+ * <p>In most cases, regular apps do not need to concern themselves with device
+ * policy, and restrictions will be enforced automatically. There are some cases
+ * where an app may wish to query device policy to provide a better user
+ * experience. Only a small number of policies allow apps to query them directly.
+ * These APIs will typically have no special required permissions.
+ *
+ * <p id="managedprovisioning"><b>Managed Provisioning</b>
+ * <p>Managed Provisioning is the process of recognising an app as a
+ * <a href="#deviceowner">Device Owner</a> or <a href="#profileowner">Profile Owner</a>. It
+ * involves presenting education and consent screens to the user to ensure they
+ * are aware of the capabilities this grants the <a href="#devicepolicycontroller">Device Policy
+ * Controller</a>
+ *
+ * <p>For more information on provisioning, see <a href="{@docRoot}work/dpc/build-dpc">Building a
+ * Device Policy Controller</a>.
+ *
+ * <p id="managed_profile">A <b>Managed Profile</b> enables data separation. For example to use
+ * a device both for personal and corporate usage. The managed profile and its
+ * {@link UserManager#getProfileParent parent} share a launcher.
+ *
+ * <p id="affiliated"><b>Affiliation</b>
+ * <p>Using the {@link #setAffiliationIds} method, a
+ * <a href="#deviceowner">Device Owner</a> can set a list of affiliation ids for the
+ * {@link UserManager#isSystemUser System User}. Any <a href="#profileowner">Profile Owner</a> on
+ * the same device can also call {@link #setAffiliationIds} to set affiliation ids
+ * for the {@link UserManager user} it is on. When there is the same ID
+ * present in both lists, the user is said to be "affiliated" and we can refer to
+ * the <a href="#profileowner">Profile Owner</a> as a "profile owner on an affiliated
+ * user" or an "affiliated profile owner".
+ *
+ * Becoming affiliated grants the <a href="#profileowner">Profile Owner</a> capabilities similar to
+ * that of the <a href="#deviceowner">Device Owner</a>. It also allows use of the
+ * {@link #bindDeviceAdminServiceAsUser} APIs for direct communication between the
+ * <a href="#deviceowner">Device Owner</a> and
+ * affiliated <a href="#profileowner">Profile Owners</a>.
+ *
+ * <p id="organization-owned"><b>Organization Owned</b></p>
+ * An organization owned device is one which is not owned by the person making use of the device and
+ * is instead owned by an organization such as their employer or education provider. These devices
+ * are recognised as being organization owned either by the presence of a
+ * <a href="#deviceowner">device owner</a> or of a
+ * {@link #isOrganizationOwnedDeviceWithManagedProfile profile which has a profile owner is marked
+ * as organization owned}.
+ *
+ * <p id="profile-on-parent-organization-owned">Profile owners running on an
+ * <a href="organization-owned">organization owned</a> device can exercise additional capabilities
+ * using the {@link #getParentProfileInstance(ComponentName)} API which apply to the parent user.
+ * Each API will indicate if it is usable in this way.
+ *
+ * <p id="automotive"><b>Android Automotive</b>
+ * <p>On {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE
+ * "Android Automotive builds"}, some methods can throw
+ * {@link UnsafeStateException "an exception"} if an action is unsafe (for example, if the vehicle
+ * is moving). Callers running on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE
+ * "Android Automotive builds"} should always check for this exception.
*/
+
@SystemService(Context.DEVICE_POLICY_SERVICE)
@RequiresFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-@SuppressLint("UseIcu")
public class DevicePolicyManager {
/** @hide */
@@ -257,7 +359,7 @@ public class DevicePolicyManager {
* Fetch the current value of mService. This is used in the binder cache lambda
* expressions.
*/
- private final IDevicePolicyManager getService() {
+ private IDevicePolicyManager getService() {
return mService;
}
@@ -265,7 +367,7 @@ public class DevicePolicyManager {
* Fetch the current value of mParentInstance. This is used in the binder cache
* lambda expressions.
*/
- private final boolean isParentInstance() {
+ private boolean isParentInstance() {
return mParentInstance;
}
@@ -273,7 +375,7 @@ public class DevicePolicyManager {
* Fetch the current value of mContext. This is used in the binder cache lambda
* expressions.
*/
- private final Context getContext() {
+ private Context getContext() {
return mContext;
}
@@ -284,39 +386,80 @@ public class DevicePolicyManager {
}
/**
- * Activity action: Starts the provisioning flow which sets up a managed profile.
- *
- * <p>A managed profile allows data separation for example for the usage of a
- * device as a personal and corporate device. The user which provisioning is started from and
- * the managed profile share a launcher.
- *
- * <p>This intent will typically be sent by a mobile device management application (MDM).
- * Provisioning adds a managed profile and sets the MDM as the profile owner who has full
- * control over the profile.
+ * Activity action: Starts the provisioning flow which sets up a
+ * <a href="#managed-profile">managed profile</a>.
*
* <p>It is possible to check if provisioning is allowed or not by querying the method
* {@link #isProvisioningAllowed(String)}.
*
- * <p>In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
- * extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
- * As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
- * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
- * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
- *
- * <p>The intent may also contain the following extras:
- * <ul>
- * <li>{@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}, optional </li>
- * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional, supported from
- * {@link android.os.Build.VERSION_CODES#N}</li>
- * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
- * </ul>
- *
- * <p>When managed provisioning has completed, broadcasts are sent to the application specified
- * in the provisioning intent. The
+ * <p>The intent may contain the following extras:
+ *
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Extra</th>
+ * <th></th>
+ * <th>Supported Versions</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}</td>
+ * <td colspan="2"></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}</td>
+ * <td></td>
+ * <td>{@link android.os.Build.VERSION_CODES#N}+</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}</td>
+ * <td colspan="2"></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_LOGO_URI}</td>
+ * <td colspan="2"></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}</td>
+ * <td colspan="2"><b>Can only be used by an existing device owner trying to create a
+ * managed profile</b></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}</td>
+ * <td colspan="2"></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_DISCLAIMERS}</td>
+ * <td colspan="2"></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}</td>
+ * <td>
+ * <b>Required if {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} is not
+ * specified. Must match the package name of the calling application.</b>
+ * </td>
+ * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}+</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</td>
+ * <td>
+ * <b>Required if {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is not
+ * specified. Package name must match the package name of the calling
+ * application.</b>
+ * </td>
+ * <td>{@link android.os.Build.VERSION_CODES#M}+</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_PROVISIONING_ALLOW_OFFLINE}</td>
+ * <td colspan="2">On {@link android.os.Build.VERSION_CODES#TIRAMISU}+, when set to
+ * true this will <b>force</b> offline provisioning instead of allowing it</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p>When <a href="#managedprovisioning">managed provisioning</a> has completed, broadcasts
+ * are sent to the application specified in the provisioning intent. The
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} broadcast is sent in the
* managed profile and the {@link #ACTION_MANAGED_PROFILE_PROVISIONED} broadcast is sent in
* the primary profile.
@@ -325,25 +468,25 @@ public class DevicePolicyManager {
* completed, along with the above broadcast, activity intent
* {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the profile owner.
*
- * <p>If provisioning fails, the managedProfile is removed so the device returns to its
+ * <p>If provisioning fails, the managed profile is removed so the device returns to its
* previous state.
*
* <p>If launched with {@link android.app.Activity#startActivityForResult(Intent, int)} a
- * result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part of
+ * result code of {@link android.app.Activity#RESULT_OK} indicates that the synchronous part of
* the provisioning flow was successful, although this doesn't guarantee the full flow will
- * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
- * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
+ * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} indicates
+ * that the user backed-out of provisioning or some precondition for provisioning wasn't met.
*
- * <p>If a device policy management role holder (DPMRH) updater is present on the device, an
- * internet connection attempt must be made prior to launching this intent. If internet
- * connection could not be established, provisioning will fail unless {@link
+ * <p>If a <a href="#roleholder">device policy management role holder</a> updater is present on
+ * the device, an internet connection attempt must be made prior to launching this intent. If
+ * an internet connection can not be established, provisioning will fail unless {@link
* #EXTRA_PROVISIONING_ALLOW_OFFLINE} is explicitly set to {@code true}, in which case
- * provisioning will continue without using the DPMRH. If an internet connection has been
- * established, the DPMRH updater will be launched, which will update the DPMRH if it's not
- * present on the device, or if it's present and not valid.
- *
- * <p>If a DPMRH is present on the device and valid, the provisioning flow will be deferred to
- * it.
+ * provisioning will continue without using the
+ * <a href="#roleholder">device policy management role holder</a>. If an internet connection
+ * has been established, the <a href="#roleholder">device policy management role holder</a>
+ * updater will be launched, which may update the
+ * <a href="#roleholder">device policy management role holder</a> before continuing
+ * provisioning.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_MANAGED_PROFILE
@@ -822,31 +965,25 @@ public class DevicePolicyManager {
"android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
/**
- * A boolean extra indicating whether offline provisioning is allowed.
- *
- * <p>For the online provisioning flow, there will be an attempt to download and install
- * the latest version of the device policy management role holder. The platform will then
- * delegate provisioning to the device policy management role holder via role holder-specific
- * provisioning actions.
- *
- * <p>For the offline provisioning flow, the provisioning flow will always be handled by
- * the platform.
+ * A boolean extra indicating whether offline provisioning should be used.
*
- * <p>If this extra is set to {@code false}, the provisioning flow will enforce that an
- * internet connection is established, which will start the online provisioning flow. If an
- * internet connection cannot be established, provisioning will fail.
+ * <p>The default value is {@code false}.
*
- * <p>If this extra is set to {@code true}, the provisioning flow will still try to connect to
- * the internet, but if it fails it will start the offline provisioning flow.
+ * <p>Usually during the <a href="#managedprovisioning">provisioning flow</a>, there will be
+ * an attempt to download and install the latest version of the <a href="#roleholder">device
+ * policy management role holder</a>. The platform will then
+ * delegate provisioning to the <a href="#roleholder">device
+ * * policy management role holder</a>.
*
- * <p>For T if this extra is set to {@code true}, the provisioning flow will be forced through
- * the platform and there will be no attempt to download and install the device policy
- * management role holder.
+ * <p>When this extra is set to {@code true}, the
+ * <a href="#managedprovisioning">provisioning flow</a> will always be handled by the platform
+ * and the <a href="#roleholder">device policy management role holder</a>'s part skipped.
*
- * <p>The default value is {@code false}.
- *
- * <p>This extra is respected when provided via the provisioning intent actions such as {@link
- * #ACTION_PROVISION_MANAGED_PROFILE}.
+ * <p>On Android versions prior to {@link Build.VERSION_CODES#TIRAMISU}, when this extra is
+ * {@code false}, the <a href="#managedprovisioning">provisioning flow</a> will enforce that an
+ * internet connection is established, or otherwise fail. When this extra is {@code true}, a
+ * connection will still be attempted but when it cannot be established provisioning will
+ * continue offline.
*/
public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE =
"android.app.extra.PROVISIONING_ALLOW_OFFLINE";
@@ -1057,64 +1194,40 @@ public class DevicePolicyManager {
public static final long DEFAULT_STRONG_AUTH_TIMEOUT_MS = 72 * 60 * 60 * 1000; // 72h
/**
- * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
- * allows a mobile device management application or NFC programmer application which starts
- * managed provisioning to pass data to the management application instance after provisioning.
+ * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that is
+ * passed directly to the <a href="#devicepolicycontroller">Device Policy Controller</a>
+ * after <a href="#managed-provisioning">provisioning</a>.
+ *
* <p>
- * If used with {@link #ACTION_PROVISION_MANAGED_PROFILE} it can be used by the application that
- * sends the intent to pass data to itself on the newly created profile.
- * If used with {@link #ACTION_PROVISION_MANAGED_DEVICE} it allows passing data to the same
- * instance of the app on the primary user.
* Starting from {@link android.os.Build.VERSION_CODES#M}, if used with
* {@link #MIME_TYPE_PROVISIONING_NFC} as part of NFC managed device provisioning, the NFC
* message should contain a stringified {@link java.util.Properties} instance, whose string
* properties will be converted into a {@link android.os.PersistableBundle} and passed to the
* management application after provisioning.
- *
- * <p>Admin apps will receive this extra in their {@link #ACTION_GET_PROVISIONING_MODE} and
- * {@link #ACTION_ADMIN_POLICY_COMPLIANCE} intent handlers. Additionally, {@link
- * #ACTION_GET_PROVISIONING_MODE} may also return this extra which will then be sent over to
- * {@link #ACTION_ADMIN_POLICY_COMPLIANCE}, alongside the original values that were passed to
- * {@link #ACTION_GET_PROVISIONING_MODE}.
- *
- * <p>
- * In both cases the application receives the data in
- * {@link DeviceAdminReceiver#onProfileProvisioningComplete} via an intent with the action
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}. The bundle is not changed
- * during the managed provisioning.
*/
public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE =
"android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
/**
- * A String extra holding the package name of the mobile device management application that
- * will be set as the profile owner or device owner.
+ * A String extra holding the package name of the application that
+ * will be set as <a href="#devicepolicycontroller">Device Policy Controller</a>.
*
- * <p>If an application starts provisioning directly via an intent with action
- * {@link #ACTION_PROVISION_MANAGED_PROFILE} this package has to match the package name of the
- * application that started provisioning. The package will be set as profile owner in that case.
+ * <p>When this extra is set, the application must have exactly one
+ * {@link DeviceAdminReceiver device admin receiver}. This receiver will be set as the
+ * <a href="#devicepolicycontroller">Device Policy Controller</a>.
*
- * <p>This package is set as device owner when device owner provisioning is started by an NFC
- * message containing an NFC record with MIME type {@link #MIME_TYPE_PROVISIONING_NFC}.
- *
- * <p> When this extra is set, the application must have exactly one device admin receiver.
- * This receiver will be set as the profile or device owner and active admin.
- *
- * @see DeviceAdminReceiver
- * @deprecated Use {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}. This extra is still
- * supported, but only if there is only one device admin receiver in the package that requires
- * the permission {@link android.Manifest.permission#BIND_DEVICE_ADMIN}.
+ * @deprecated Use {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}.
*/
@Deprecated
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
/**
- * A ComponentName extra indicating the device admin receiver of the mobile device management
- * application that will be set as the profile owner or device owner and active admin.
+ * A ComponentName extra indicating the {@link DeviceAdminReceiver device admin receiver} of
+ * the application that will be set as the <a href="#devicepolicycontroller">
+ * Device Policy Controller</a>.
*
* <p>If an application starts provisioning directly via an intent with action
- * {@link #ACTION_PROVISION_MANAGED_PROFILE} or
* {@link #ACTION_PROVISION_MANAGED_DEVICE} the package name of this
* component has to match the package name of the application that started provisioning.
*
@@ -1123,35 +1236,28 @@ public class DevicePolicyManager {
* message containing an NFC record with MIME type
* {@link #MIME_TYPE_PROVISIONING_NFC}. For the NFC record, the component name must be
* flattened to a string, via {@link ComponentName#flattenToShortString()}.
- *
- * @see DeviceAdminReceiver
*/
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
/**
* An {@link android.accounts.Account} extra holding the account to migrate during managed
- * profile provisioning. If the account supplied is present in the primary user, it will be
- * copied, along with its credentials to the managed profile and removed from the primary user.
+ * profile provisioning.
*
- * Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}, with managed account provisioning, or
- * return as an extra to the intent result from the {@link #ACTION_GET_PROVISIONING_MODE}
- * activity.
+ * <p>If the account supplied is present in the user, it will be copied, along with its
+ * credentials to the managed profile and removed from the user.
*/
-
public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE
= "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
/**
- * Boolean extra to indicate that the migrated account should be kept. This is used in
- * conjunction with {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}. If it's set to {@code true},
- * the account will not be removed from the primary user after it is migrated to the newly
- * created user or profile.
+ * Boolean extra to indicate that the
+ * {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE migrated account} should be kept.
*
- * <p> Defaults to {@code false}
+ * <p>If it's set to {@code true}, the account will not be removed from the user after it is
+ * migrated to the newly created user or profile.
*
- * <p> Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or set as an extra to the
- * intent result of the {@link #ACTION_GET_PROVISIONING_MODE} activity.
+ * <p>Defaults to {@code false}
*
* @see #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE
*/
@@ -1590,17 +1696,14 @@ public class DevicePolicyManager {
"android.app.action.PROVISIONING_SUCCESSFUL";
/**
- * A boolean extra indicating whether device encryption can be skipped as part of device owner
- * or managed profile provisioning.
+ * A boolean extra indicating whether device encryption can be skipped as part of
+ * <a href="#managed-provisioning>provisioning</a>.
*
* <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action
* {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning.
*
* <p>From {@link android.os.Build.VERSION_CODES#N} onwards, this is also supported for an
* intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}.
- *
- * <p>This extra can also be returned by the admin app when performing the admin-integrated
- * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity.
*/
public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION =
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
@@ -1608,23 +1711,22 @@ public class DevicePolicyManager {
/**
* A {@link Uri} extra pointing to a logo image. This image will be shown during the
* provisioning. If this extra is not passed, a default image will be shown.
- * <h5>The following URI schemes are accepted:</h5>
+ *
+ * <p><b>The following URI schemes are accepted:</b>
* <ul>
* <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
* <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
* </ul>
*
- * <p> It is the responsibility of the caller to provide an image with a reasonable
+ * <p>It is the responsibility of the caller to provide an image with a reasonable
* pixel density for the device.
*
- * <p> If a content: URI is passed, the intent should have the flag
+ * <p>If a content: URI is passed, the intent should also have the flag
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
- * {@link android.content.ClipData} of the intent too.
+ * {@link android.content.ClipData} of the intent.
*
- * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
- * {@link #ACTION_PROVISION_MANAGED_DEVICE}
- *
- * @deprecated Logo customization is no longer supported in the provisioning flow.
+ * @deprecated Logo customization is no longer supported in the
+ * <a href="#managedprovisioning">provisioning flow</a>.
*/
@Deprecated
public static final String EXTRA_PROVISIONING_LOGO_URI =
@@ -1632,7 +1734,8 @@ public class DevicePolicyManager {
/**
* A {@link Bundle}[] extra consisting of list of disclaimer headers and disclaimer contents.
- * Each {@link Bundle} must have both {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}
+ *
+ * <p>Each {@link Bundle} must have both {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}
* as disclaimer header, and {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} as disclaimer
* content.
*
@@ -1653,20 +1756,21 @@ public class DevicePolicyManager {
/**
* A String extra of localized disclaimer header.
*
- * <p> The extra is typically the company name of mobile device management application (MDM)
+ * <p>The extra is typically the company name of mobile device management application (MDM)
* or the organization name.
*
- * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
- *
- * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
- * disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
- * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT}. Here is the example:
+ * <p>{@link ApplicationInfo#FLAG_SYSTEM System apps} can also insert a disclaimer by declaring
+ * an application-level meta-data in {@code AndroidManifest.xml}.
*
+ * <p>For example:
* <pre>
* &lt;meta-data
* android:name="android.app.extra.PROVISIONING_DISCLAIMER_HEADER"
* android:resource="@string/disclaimer_header"
* /&gt;</pre>
+ *
+ * <p>This must be accompanied with another extra using the key
+ * {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT}.
*/
public static final String EXTRA_PROVISIONING_DISCLAIMER_HEADER =
"android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
@@ -1680,35 +1784,35 @@ public class DevicePolicyManager {
* <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
* </ul>
*
- * <p> Styled text is supported in the disclaimer content. The content is parsed by
- * {@link android.text.Html#fromHtml(String)} and displayed in a
- * {@link android.widget.TextView}.
+ * <p>Styled text is supported. This is parsed by {@link android.text.Html#fromHtml(String)}
+ * and displayed in a {@link android.widget.TextView}.
*
- * <p> If a <code>content:</code> URI is passed, URI is passed, the intent should have the flag
- * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
- * {@link android.content.ClipData} of the intent too.
+ * <p>If a <code>content:</code> URI is passed, the intent should also have the
+ * flag {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
+ * {@link android.content.ClipData} of the intent.
*
- * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
- *
- * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
+ * <p>{@link ApplicationInfo#FLAG_SYSTEM System apps} can also insert a
* disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
- * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}. Here is the example:
+ *
+ * <p>For example:
*
* <pre>
* &lt;meta-data
* android:name="android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"
* android:resource="@string/disclaimer_content"
* /&gt;</pre>
+ *
+ * <p>This must be accompanied with another extra using the key
+ * {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}.
*/
public static final String EXTRA_PROVISIONING_DISCLAIMER_CONTENT =
"android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
/**
- * A boolean extra indicating if the user consent steps from the provisioning flow should be
- * skipped. If unspecified, defaults to {@code false}.
+ * A boolean extra indicating if the user consent steps from the
+ * <a href="#managed-provisioning">provisioning flow</a> should be skipped.
*
- * It can only be used by an existing device owner trying to create a managed profile via
- * {@link #ACTION_PROVISION_MANAGED_PROFILE}. Otherwise it is ignored.
+ * <p>If unspecified, defaults to {@code false}.
*
* @deprecated this extra is no longer relevant as device owners cannot create managed profiles
*/
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index a51a74075298..d9a18d785537 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -42,12 +42,6 @@ public class DdmHandleHello extends DdmHandle {
private static DdmHandleHello mInstance = new DdmHandleHello();
- private static final String[] FRAMEWORK_FEATURES = new String[] {
- "opengl-tracing",
- "view-hierarchy",
- "support_boot_stages"
- };
-
/* singleton, do not instantiate */
private DdmHandleHello() {}
@@ -193,22 +187,25 @@ public class DdmHandleHello extends DdmHandle {
if (false)
Log.v("ddm-heap", "Got feature list request");
- int size = 4 + 4 * (vmFeatures.length + FRAMEWORK_FEATURES.length);
- for (int i = vmFeatures.length-1; i >= 0; i--)
+ String[] fmFeatures = Debug.getFeatureList();
+ int size = 4 + 4 * (vmFeatures.length + fmFeatures.length);
+ for (int i = vmFeatures.length - 1; i >= 0; i--) {
size += vmFeatures[i].length() * 2;
- for (int i = FRAMEWORK_FEATURES.length-1; i>= 0; i--)
- size += FRAMEWORK_FEATURES[i].length() * 2;
+ }
+ for (int i = fmFeatures.length - 1; i >= 0; i--) {
+ size += fmFeatures[i].length() * 2;
+ }
ByteBuffer out = ByteBuffer.allocate(size);
out.order(ChunkHandler.CHUNK_ORDER);
- out.putInt(vmFeatures.length + FRAMEWORK_FEATURES.length);
+ out.putInt(vmFeatures.length + fmFeatures.length);
for (int i = vmFeatures.length-1; i >= 0; i--) {
out.putInt(vmFeatures[i].length());
putString(out, vmFeatures[i]);
}
- for (int i = FRAMEWORK_FEATURES.length-1; i >= 0; i--) {
- out.putInt(FRAMEWORK_FEATURES[i].length());
- putString(out, FRAMEWORK_FEATURES[i]);
+ for (int i = fmFeatures.length - 1; i >= 0; i--) {
+ out.putInt(fmFeatures[i].length());
+ putString(out, fmFeatures[i]);
}
return new Chunk(CHUNK_FEAT, out);
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index f785cca4e9f4..a55398ac9752 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -110,6 +110,12 @@ public final class Debug
private static final String DEFAULT_TRACE_BODY = "dmtrace";
private static final String DEFAULT_TRACE_EXTENSION = ".trace";
+ private static final String[] FRAMEWORK_FEATURES = new String[] {
+ "opengl-tracing",
+ "view-hierarchy",
+ "support_boot_stages",
+ };
+
/**
* This class is used to retrieved various statistics about the memory mappings for this
* process. The returned info is broken down by dalvik, native, and other. All results are in kB.
@@ -1106,6 +1112,17 @@ public final class Debug
}
/**
+ * Returns an array of strings that identify Framework features. This is
+ * used by DDMS to determine what sorts of operations the Framework can
+ * perform.
+ *
+ * @hide
+ */
+ public static String[] getFeatureList() {
+ return FRAMEWORK_FEATURES;
+ }
+
+ /**
* Change the JDWP port.
*
* @deprecated no longer needed or useful
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 17d14042e468..7dc151d8f9ee 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -288,7 +288,7 @@ public final class PointerIcon implements Parcelable {
if (bitmap == null) {
throw new IllegalArgumentException("bitmap must not be null");
}
- validateHotSpot(bitmap, hotSpotX, hotSpotY);
+ validateHotSpot(bitmap, hotSpotX, hotSpotY, false /* isScaled */);
PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
icon.mBitmap = bitmap;
@@ -521,7 +521,9 @@ public final class PointerIcon implements Parcelable {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
final Bitmap bitmap = getBitmapFromDrawable(bitmapDrawable);
- validateHotSpot(bitmap, hotSpotX, hotSpotY);
+ // The bitmap and hotspot are loaded from the context, which means it is implicitly scaled
+ // to the current display density, so treat this as a scaled icon when verifying hotspot.
+ validateHotSpot(bitmap, hotSpotX, hotSpotY, true /* isScaled */);
// Set the properties now that we have successfully loaded the icon.
mBitmap = bitmap;
mHotSpotX = hotSpotX;
@@ -535,11 +537,16 @@ public final class PointerIcon implements Parcelable {
+ ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY + "}";
}
- private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
- if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+ private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY,
+ boolean isScaled) {
+ // Be more lenient when checking the hotspot for scaled icons to account for the restriction
+ // that bitmaps must have an integer size.
+ if (hotSpotX < 0 || (isScaled ? (int) hotSpotX > bitmap.getWidth()
+ : hotSpotX >= bitmap.getWidth())) {
throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
}
- if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+ if (hotSpotY < 0 || (isScaled ? (int) hotSpotY > bitmap.getHeight()
+ : hotSpotY >= bitmap.getHeight())) {
throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
}
}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 4816f35e6a07..b1cf8340cc25 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -72,8 +72,17 @@ public final class BackNavigationInfo implements Parcelable {
/**
* Key to access the boolean value passed in {#mOnBackNavigationDone} result bundle
* that represents if back navigation has been triggered.
+ * @hide
+ */
+ public static final String KEY_NAVIGATION_FINISHED = "NavigationFinished";
+
+ /**
+ * Key to access the boolean value passed in {#mOnBackNavigationDone} result bundle
+ * that represents if back gesture has been triggered.
+ * @hide
*/
- public static final String KEY_TRIGGER_BACK = "TriggerBack";
+ public static final String KEY_GESTURE_FINISHED = "GestureFinished";
+
/**
* Defines the type of back destinations a back even can lead to. This is used to define the
@@ -192,7 +201,21 @@ public final class BackNavigationInfo implements Parcelable {
public void onBackNavigationFinished(boolean triggerBack) {
if (mOnBackNavigationDone != null) {
Bundle result = new Bundle();
- result.putBoolean(KEY_TRIGGER_BACK, triggerBack);
+ result.putBoolean(KEY_NAVIGATION_FINISHED, triggerBack);
+ mOnBackNavigationDone.sendResult(result);
+ }
+ }
+
+ /**
+ * Callback to be called when the back gesture is finished in order to notify the server that
+ * it can ask app to start rendering.
+ * @hide
+ * @param triggerBack Boolean indicating if back gesture has been triggered.
+ */
+ public void onBackGestureFinished(boolean triggerBack) {
+ if (mOnBackNavigationDone != null) {
+ Bundle result = new Bundle();
+ result.putBoolean(KEY_GESTURE_FINISHED, triggerBack);
mOnBackNavigationDone.sendResult(result);
}
}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 93297e64c621..89327fe358f5 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -18,10 +18,13 @@ package android.window;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
@@ -101,11 +104,20 @@ public final class TaskFragmentCreationParams implements Parcelable {
*/
private final boolean mAllowTransitionWhenEmpty;
+ /**
+ * The override orientation for the TaskFragment. This is effective only for a system organizer.
+ * The value is ignored otherwise. Default to {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ */
+ private final @ScreenOrientation int mOverrideOrientation;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
+ @ScreenOrientation int overrideOrientation) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -118,6 +130,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ mOverrideOrientation = overrideOrientation;
}
@NonNull
@@ -168,6 +181,11 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mAllowTransitionWhenEmpty;
}
+ /** @hide */
+ public @ScreenOrientation int getOverrideOrientation() {
+ return mOverrideOrientation;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -177,6 +195,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
mAllowTransitionWhenEmpty = in.readBoolean();
+ mOverrideOrientation = in.readInt();
}
/** @hide */
@@ -190,6 +209,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
dest.writeBoolean(mAllowTransitionWhenEmpty);
+ dest.writeInt(mOverrideOrientation);
}
@NonNull
@@ -217,6 +237,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ + " overrideOrientation=" + mOverrideOrientation
+ "}";
}
@@ -252,6 +273,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
private boolean mAllowTransitionWhenEmpty;
+ private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -330,12 +353,28 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets the override orientation for the TaskFragment. This is effective only for a system
+ * organizer. The value is ignored otherwise. Default to
+ * {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @NonNull
+ public Builder setOverrideOrientation(@ScreenOrientation int overrideOrientation) {
+ mOverrideOrientation = overrideOrientation;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken, mAllowTransitionWhenEmpty);
+ mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
}
}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bcbac9319887..47a4052df95c 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -36,6 +36,8 @@ import android.view.ImeBackAnimationController;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -75,14 +77,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Nullable
private ImeBackAnimationController mImeBackAnimationController;
+ @GuardedBy("mLock")
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
@VisibleForTesting
+ @GuardedBy("mLock")
public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private Checker mChecker;
+ private final Object mLock = new Object();
public WindowOnBackInvokedDispatcher(@NonNull Context context) {
mChecker = new Checker(context);
@@ -94,20 +99,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
*/
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
@Nullable ImeBackAnimationController imeBackAnimationController) {
- mWindowSession = windowSession;
- mWindow = window;
- mImeBackAnimationController = imeBackAnimationController;
- if (!mAllCallbacks.isEmpty()) {
- setTopOnBackInvokedCallback(getTopCallback());
+ synchronized (mLock) {
+ mWindowSession = windowSession;
+ mWindow = window;
+ mImeBackAnimationController = imeBackAnimationController;
+ if (!mAllCallbacks.isEmpty()) {
+ setTopOnBackInvokedCallback(getTopCallback());
+ }
}
}
/** Detaches the dispatcher instance from its window. */
public void detachFromWindow() {
- clear();
- mWindow = null;
- mWindowSession = null;
- mImeBackAnimationController = null;
+ synchronized (mLock) {
+ clear();
+ mWindow = null;
+ mWindowSession = null;
+ mImeBackAnimationController = null;
+ }
}
// TODO: Take an Executor for the callback to run on.
@@ -125,65 +134,71 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
*/
public void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
- if (mImeDispatcher != null) {
- mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
- return;
- }
- if (!mOnBackInvokedCallbacks.containsKey(priority)) {
- mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+ return;
+ }
+ if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+ mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+ }
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- // If callback has already been added, remove it and re-add it.
- if (mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ // If callback has already been added, remove it and re-add it.
+ if (mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ }
+ Integer prevPriority = mAllCallbacks.get(callback);
+ mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
}
- Integer prevPriority = mAllCallbacks.get(callback);
- mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
- }
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- callbacks.add(callback);
- mAllCallbacks.put(callback, priority);
- if (previousTopCallback == null
- || (previousTopCallback != callback
- && mAllCallbacks.get(previousTopCallback) <= priority)) {
- setTopOnBackInvokedCallback(callback);
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ callbacks.add(callback);
+ mAllCallbacks.put(callback, priority);
+ if (previousTopCallback == null
+ || (previousTopCallback != callback
+ && mAllCallbacks.get(previousTopCallback) <= priority)) {
+ setTopOnBackInvokedCallback(callback);
+ }
}
}
@Override
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
- if (mImeDispatcher != null) {
- mImeDispatcher.unregisterOnBackInvokedCallback(callback);
- return;
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- if (!mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback not found. returning...");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+ return;
+ }
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ if (!mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback not found. returning...");
+ }
+ return;
+ }
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ Integer priority = mAllCallbacks.get(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
+ mAllCallbacks.remove(callback);
+ // Re-populate the top callback to WM if the removed callback was previously the top
+ // one.
+ if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from
+ // dispatcher.
+ sendCancelledIfInProgress(callback);
+ setTopOnBackInvokedCallback(getTopCallback());
}
- return;
- }
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- Integer priority = mAllCallbacks.get(callback);
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- callbacks.remove(callback);
- if (callbacks.isEmpty()) {
- mOnBackInvokedCallbacks.remove(priority);
- }
- mAllCallbacks.remove(callback);
- // Re-populate the top callback to WM if the removed callback was previously the top one.
- if (previousTopCallback == callback) {
- // We should call onBackCancelled() when an active callback is removed from dispatcher.
- sendCancelledIfInProgress(callback);
- setTopOnBackInvokedCallback(getTopCallback());
}
}
@@ -191,15 +206,21 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
* Indicates if the dispatcher is actively dispatching to a callback.
*/
public boolean isDispatching() {
- return mIsDispatching;
+ synchronized (mLock) {
+ return mIsDispatching;
+ }
}
private void onStartDispatching() {
- mIsDispatching = true;
+ synchronized (mLock) {
+ mIsDispatching = true;
+ }
}
private void onStopDispatching() {
- mIsDispatching = false;
+ synchronized (mLock) {
+ mIsDispatching = false;
+ }
}
private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
@@ -223,27 +244,29 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
/** Clears all registered callbacks on the instance. */
public void clear() {
- if (mImeDispatcher != null) {
- mImeDispatcher.clear();
- mImeDispatcher = null;
- }
- if (!mAllCallbacks.isEmpty()) {
- OnBackInvokedCallback topCallback = getTopCallback();
- if (topCallback != null) {
- sendCancelledIfInProgress(topCallback);
- } else {
- // Should not be possible
- Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.clear();
+ mImeDispatcher = null;
+ }
+ if (!mAllCallbacks.isEmpty()) {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ if (topCallback != null) {
+ sendCancelledIfInProgress(topCallback);
+ } else {
+ // Should not be possible
+ Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ }
+ // Clear binder references in WM.
+ setTopOnBackInvokedCallback(null);
}
- // Clear binder references in WM.
- setTopOnBackInvokedCallback(null);
- }
- // We should also stop running animations since all callbacks have been removed.
- // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
- Handler.getMain().post(mProgressAnimator::reset);
- mAllCallbacks.clear();
- mOnBackInvokedCallbacks.clear();
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
+ mAllCallbacks.clear();
+ mOnBackInvokedCallbacks.clear();
+ }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -275,13 +298,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
public OnBackInvokedCallback getTopCallback() {
- if (mAllCallbacks.isEmpty()) {
- return null;
- }
- for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- if (!callbacks.isEmpty()) {
- return callbacks.get(callbacks.size() - 1);
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ return null;
+ }
+ for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ if (!callbacks.isEmpty()) {
+ return callbacks.get(callbacks.size() - 1);
+ }
}
}
return null;
@@ -315,16 +340,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
writer.println(prefix + "WindowOnBackDispatcher:");
- if (mAllCallbacks.isEmpty()) {
- writer.println(prefix + "<None>");
- return;
- }
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
- writer.println(innerPrefix + "Top Callback: " + getTopCallback());
- writer.println(innerPrefix + "Callbacks: ");
- mAllCallbacks.forEach((callback, priority) -> {
- writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
- });
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
}
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index e531bcbaa215..06ae11fee847 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -135,7 +135,7 @@ public class AccessibilityShortcutController {
DialogStatus.SHOWN,
})
/** Denotes the user shortcut type. */
- @interface DialogStatus {
+ public @interface DialogStatus {
int NOT_SHOWN = 0;
int SHOWN = 1;
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index cb1abf13c109..bdb33c4b151c 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,12 +16,10 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,8 +59,6 @@ public class ListMenuItemView extends LinearLayout
private int mMenuType;
- private boolean mUseNewContextMenu;
-
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -89,10 +85,6 @@ public class ListMenuItemView extends LinearLayout
a.recycle();
b.recycle();
-
- mUseNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -289,9 +281,7 @@ public class ListMenuItemView extends LinearLayout
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(
- mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
- com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index 1979e4fe7a90..36828f2dadca 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -16,24 +16,26 @@
package com.android.internal.view.menu;
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
+import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnKeyListener;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.MenuPopupWindow;
import android.widget.PopupWindow;
-import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
import java.util.Objects;
@@ -44,6 +46,8 @@ import java.util.Objects;
final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener,
MenuPresenter, OnKeyListener {
private static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+ private static final int ITEM_LAYOUT_MATERIAL =
+ com.android.internal.R.layout.popup_menu_item_layout_material;
private final Context mContext;
@@ -53,6 +57,7 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On
private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
+
// The popup window is final in order to couple its lifecycle to the lifecycle of the
// StandardMenuPopup.
private final MenuPopupWindow mPopup;
@@ -114,10 +119,15 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
mContext = Objects.requireNonNull(context);
+ boolean useNewContextMenu = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
+
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
- mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
+ useNewContextMenu ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c694426a5aa4..ab714ad7d807 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -581,6 +581,7 @@
<protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+ <protected-broadcast android:name="com.android.server.notification.TimeToLiveHelper" />
<protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
index a30be6a13db6..d8514608e8dd 100644
--- a/core/res/res/layout/list_menu_item_icon.xml
+++ b/core/res/res/layout/list_menu_item_icon.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dip"
- android:layout_marginEnd="-8dip"
+ android:layout_marginEnd="8dip"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
android:scaleType="centerInside"
diff --git a/core/res/res/layout/popup_menu_item_layout_material.xml b/core/res/res/layout/popup_menu_item_layout_material.xml
new file mode 100644
index 000000000000..e20ead62032c
--- /dev/null
+++ b/core/res/res/layout/popup_menu_item_layout_material.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+
+ Forked from the popup_menu_item_layout.xml for material support. When you edit this file, you
+ may also need to update that file.
+-->
+
+<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minWidth="196dip"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@+id/group_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:layout_marginTop="4dip"
+ android:layout_marginBottom="4dip"
+ android:background="@drawable/list_divider_material" />
+
+ <LinearLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/dropdownListPreferredItemHeight"
+ android:paddingEnd="16dip"
+ android:duplicateParentState="true" >
+
+ <!-- Icon will be inserted here. -->
+
+ <!-- The title and summary have some gap between them,
+ and this 'group' should be centered vertically. -->
+ <RelativeLayout
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:duplicateParentState="true">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:textAppearance="?attr/textAppearanceLargePopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:textAlignment="viewStart" />
+
+ <TextView
+ android:id="@+id/shortcut"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignParentStart="true"
+ android:textAppearance="?attr/textAppearanceSmallPopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:textAlignment="viewStart" />
+
+ </RelativeLayout>
+
+ <ImageView
+ android:id="@+id/submenuarrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
+ android:scaleType="center"
+ android:visibility="gone" />
+
+ <!-- Checkbox, and/or radio button will be inserted here. -->
+
+ </LinearLayout>
+
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2115f64a60b3..a622d36edc6a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7015,4 +7015,9 @@
<!-- Frame rate compatibility value for Wallpaper
FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption -->
<integer name="config_wallpaperFrameRateCompatibility">102</integer>
+
+ <!-- Min time in milliseconds to complete an emergency gesture for it count.
+ If the gesture is completed faster than this, we assume it's not performed by human and the
+ event gets ignored. -->
+ <integer name="config_defaultMinEmergencyGestureTapDurationMillis">200</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9e0954093eb2..c4033f2d680a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1527,6 +1527,7 @@
<java-symbol type="layout" name="number_picker" />
<java-symbol type="layout" name="permissions_package_list_item" />
<java-symbol type="layout" name="popup_menu_item_layout" />
+ <java-symbol type="layout" name="popup_menu_item_layout_material" />
<java-symbol type="layout" name="popup_menu_header_item_layout" />
<java-symbol type="layout" name="remote_views_adapter_default_loading_view" />
<java-symbol type="layout" name="search_bar" />
@@ -5401,4 +5402,6 @@
<!-- Frame rate compatibility value for Wallpaper -->
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
+
+ <java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 404e8731d5c5..04e90baaff3a 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -154,6 +154,12 @@ android_app {
"android.test.runner",
"org.apache.http.legacy",
],
+ uses_libs: [
+ "android.test.runner",
+ ],
+ optional_uses_libs: [
+ "org.apache.http.legacy",
+ ],
sdk_version: "core_platform",
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2f2215fd51a2..d1d7c145680f 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,8 +16,6 @@
package android.security;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* This class provides some constants and helper methods related to Android's Keystore service.
* This class was originally much larger, but its functionality was superseded by other classes.
@@ -30,11 +28,4 @@ public class KeyStore {
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
-
- private static final KeyStore KEY_STORE = new KeyStore();
-
- @UnsupportedAppUsage
- public static KeyStore getInstance() {
- return KEY_STORE;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 73b2656d596a..d3fe4f82daf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -837,6 +837,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// The next callback should be {@link #onBackAnimationFinished}.
if (mCurrentTracker.getTriggerBack()) {
+ // notify gesture finished
+ mBackNavigationInfo.onBackGestureFinished(true);
dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker);
} else {
tryDispatchOnBackCancelled(mActiveCallback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index a32b435ff99e..4988a9481d21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,6 +28,7 @@ import android.view.RemoteAnimationTarget;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
@@ -108,7 +109,8 @@ public class BackAnimationRunner {
}
}
- private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ @VisibleForTesting
+ boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
return apps.length > 0 && mCujType != NO_CUJ;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 9c623bd5b76f..65169e36a225 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.back;
-import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK;
+import static android.window.BackNavigationInfo.KEY_NAVIGATION_FINISHED;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -596,6 +596,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Set up the monitoring objects.
doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(false).when(animationRunner).shouldMonitorCUJ(any());
doReturn(runner).when(animationRunner).getRunner();
doReturn(callback).when(animationRunner).getCallback();
@@ -677,7 +678,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Override
public void onResult(@Nullable Bundle result) {
mBackNavigationDone = true;
- mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+ mTriggerBack = result.getBoolean(KEY_NAVIGATION_FINISHED);
}
}
}
diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java
index bbf1e0889b68..d78828462b1e 100644
--- a/media/java/android/media/session/ParcelableListBinder.java
+++ b/media/java/android/media/session/ParcelableListBinder.java
@@ -45,6 +45,7 @@ public class ParcelableListBinder<T extends Parcelable> extends Binder {
private static final int END_OF_PARCEL = 0;
private static final int ITEM_CONTINUED = 1;
+ private final Class<T> mListElementsClass;
private final Consumer<List<T>> mConsumer;
private final Object mLock = new Object();
@@ -61,9 +62,11 @@ public class ParcelableListBinder<T extends Parcelable> extends Binder {
/**
* Creates an instance.
*
+ * @param listElementsClass the class of the list elements.
* @param consumer a consumer that consumes the list received
*/
- public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) {
+ public ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer) {
+ mListElementsClass = listElementsClass;
mConsumer = consumer;
}
@@ -83,7 +86,13 @@ public class ParcelableListBinder<T extends Parcelable> extends Binder {
mCount = data.readInt();
}
while (i < mCount && data.readInt() != END_OF_PARCEL) {
- mList.add(data.readParcelable(null));
+ Object object = data.readParcelable(null);
+ if (mListElementsClass.isAssignableFrom(object.getClass())) {
+ // Checking list items are of compaitible types to validate against malicious
+ // apps calling it directly via reflection with non compilable items.
+ // See b/317048338 for more details
+ mList.add((T) object);
+ }
i++;
}
if (i >= mCount) {
diff --git a/packages/CredentialManager/wear/res/drawable/passkey_icon.xml b/packages/CredentialManager/wear/res/drawable/passkey_icon.xml
deleted file mode 100644
index be366bf2a255..000000000000
--- a/packages/CredentialManager/wear/res/drawable/passkey_icon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:pathData="M23,10.5H17V13.5H23V10.5Z"
- android:fillColor="#188038"/>
- <path
- android:pathData="M6.5,17.5C3.5,17.5 1,15 1,12C1,9 3.5,6.5 6.5,6.5C9.5,6.5 12,9 12,12C12,15 9.5,17.5 6.5,17.5ZM6.5,9.5C5.1,9.5 4,10.6 4,12C4,13.4 5.1,14.5 6.5,14.5C7.9,14.5 9,13.4 9,12C9,10.6 7.9,9.5 6.5,9.5Z"
- android:fillColor="#4285F4"/>
- <path
- android:pathData="M21,13.5H19H17V16.5H19V15.5C19,14.9 19.4,14.5 20,14.5C20.6,14.5 21,14.9 21,15.5V16.5H23V13.5H21Z"
- android:fillColor="#34A853"/>
- <path
- android:pathData="M11.8,10.5H8.5C8.8,10.9 9,11.4 9,12C9,12.6 8.8,13.1 8.5,13.5H11.8C11.9,13 12,12.5 12,12C12,11.5 11.9,11 11.8,10.5Z"
- android:fillColor="#EA4335"/>
- <path
- android:pathData="M17,10.5H11.8C11.9,11 12,11.5 12,12C12,12.5 11.9,13 11.8,13.5H17V10.5Z"
- android:fillColor="#FBBC04"/>
-</vector>
diff --git a/packages/CredentialManager/wear/res/values-watch/donottranslate.xml b/packages/CredentialManager/wear/res/values-watch/donottranslate.xml
new file mode 100644
index 000000000000..c3ab3cbb1f27
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values-watch/donottranslate.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- font-family-device-default is expected to be preloaded in the font_customization.xml(/vendor/<OEM>/products/<PRODUCT>/fonts/fonts_customization.xml)-->
+ <!-- Falls back to system default when font-family-device-default doesn't exist -->
+ <string name="wear_material_compose_display_1_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_display_2_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_display_3_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_title_1_font_family">font-family-medium-device-default</string>
+ <string name="wear_material_compose_title_2_font_family">font-family-medium-device-default</string>
+ <string name="wear_material_compose_title_3_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_body_1_font_family">font-family-text-device-default</string>
+ <string name="wear_material_compose_body_2_font_family">font-family-text-device-default</string>
+ <string name="wear_material_compose_button_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_1_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_2_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_3_font_family">font-family-text-medium-device-default</string>
+</resources>
diff --git a/packages/CredentialManager/wear/res/values/overlayable.xml b/packages/CredentialManager/wear/res/values/overlayable.xml
new file mode 100644
index 000000000000..5b9d37259b98
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/overlayable.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <overlayable name="CredentialSelectorStyles">
+ <policy type="product|system|vendor|odm|oem">
+ <!--START WEAR SPECIFIC FONT STRINGS -->
+ <item type="string" name="wear_material_compose_display_1_font_family" />
+ <item type="string" name="wear_material_compose_display_2_font_family" />
+ <item type="string" name="wear_material_compose_display_3_font_family" />
+ <item type="string" name="wear_material_compose_title_1_font_family" />
+ <item type="string" name="wear_material_compose_title_2_font_family" />
+ <item type="string" name="wear_material_compose_title_3_font_family" />
+ <item type="string" name="wear_material_compose_body_1_font_family" />
+ <item type="string" name="wear_material_compose_body_2_font_family" />
+ <item type="string" name="wear_material_compose_button_font_family" />
+ <item type="string" name="wear_material_compose_caption_1_font_family" />
+ <item type="string" name="wear_material_compose_caption_2_font_family" />
+ <item type="string" name="wear_material_compose_caption_3_font_family" />
+ <!--END WEAR SPECIFIC FONT STRINGS -->
+
+ </policy>
+
+ </overlayable>
+
+</resources>
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
index 3422d3dc4d94..6c145631a39e 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -65,29 +65,29 @@ class CredentialSelectorUiStateGetMapperTest {
isLastUnlocked = true
)
- val passkeyCredentialEntryInfo =
+ private val passkeyCredentialEntryInfo =
createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName")
- val unknownCredentialEntryInfo =
+ private val unknownCredentialEntryInfo =
createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2")
- val passwordCredentialEntryInfo =
+ private val passwordCredentialEntryInfo =
createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName")
- val recentlyUsedPasskeyCredential =
+ private val recentlyUsedPasskeyCredential =
createCredentialEntryInfo(credentialType =
CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName")
- val recentlyUsedPasswordCredential =
+ private val recentlyUsedPasswordCredential =
createCredentialEntryInfo(credentialType =
CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName")
- val credentialList1 = listOf(
+ private val credentialList1 = listOf(
passkeyCredentialEntryInfo,
passwordCredentialEntryInfo
)
- val credentialList2 = listOf(
+ private val credentialList2 = listOf(
passkeyCredentialEntryInfo,
passwordCredentialEntryInfo,
recentlyUsedPasskeyCredential,
@@ -118,11 +118,12 @@ class CredentialSelectorUiStateGetMapperTest {
unknownCredentialEntryInfo)))).toGet(isPrimary = true)
assertThat(getCredentialUiState).isEqualTo(
- CredentialSelectorUiState.Get.SingleEntryPerAccount(
+ CredentialSelectorUiState.Get.MultipleEntryPrimaryScreen(
sortedEntries = listOf(
passkeyCredentialEntryInfo, // userName
unknownCredentialEntryInfo // userName2
),
+ icon = mDrawable,
authenticationEntryList = listOf(authenticationEntryInfo)
)) // prefer passkey from account 1, then unknown from account 2
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0fe35e695047..652e62cb26b4 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -21,7 +21,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
-import androidx.wear.compose.material.MaterialTheme
+import com.android.credentialmanager.ui.theme.WearCredentialSelectorTheme
import com.android.credentialmanager.ui.WearApp
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import dagger.hilt.android.AndroidEntryPoint
@@ -36,7 +36,7 @@ class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
super.onCreate(savedInstanceState)
setTheme(android.R.style.Theme_DeviceDefault)
setContent {
- MaterialTheme {
+ WearCredentialSelectorTheme {
WearApp(
flowEngine = viewModel,
onCloseApp = { finish() },
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index b7fa33e9372f..36085684db57 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -86,7 +86,7 @@ class CredentialSelectorViewModel @Inject constructor(
when (uiState.value) {
is Get.MultipleEntry -> isPrimaryScreen.value = true
is Create, Close, is Cancel, Idle -> shouldClose.value = true
- is Get.SingleEntry, is Get.SingleEntryPerAccount -> cancel()
+ is Get.SingleEntry, is Get.MultipleEntryPrimaryScreen -> cancel()
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
index c05fc93b8223..b2f55c108317 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager
import android.content.Intent
+import android.graphics.drawable.Drawable
import androidx.activity.result.IntentSenderRequest
import androidx.compose.runtime.Composable
import com.android.credentialmanager.model.EntryInfo
@@ -71,14 +72,14 @@ sealed class CredentialSelectorUiState {
/** Getting credential UI state when there is only one credential available. */
data class SingleEntry(val entry: CredentialEntryInfo) : Get()
/**
- * Getting credential UI state when there is only one account while with multiple
- * credentials, with different types(eg, passkey vs password) or providers.
+ * Getting credential UI state on primary screen when there is are multiple accounts.
*/
- data class SingleEntryPerAccount(
+ data class MultipleEntryPrimaryScreen(
+ val icon: Drawable?,
val sortedEntries: List<CredentialEntryInfo>,
val authenticationEntryList: List<AuthenticationEntryInfo>,
) : Get()
- /** Getting credential UI state when there are multiple accounts available. */
+ /** Getting credential UI state on secondary screen when there are multiple accounts available. */
data class MultipleEntry(
val accounts: List<PerUserNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 018db6899f6e..a75aeaff0c48 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -29,7 +29,7 @@ import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntryPrimaryScreen
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.FlowEngine
@@ -95,7 +95,7 @@ fun WearApp(
scrollable(Screen.MultipleCredentialsScreenFold.route) {
MultiCredentialsFoldScreen(
- credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount),
+ credentialSelectorUiState = (remember { uiState } as MultipleEntryPrimaryScreen),
columnState = it.columnState,
flowEngine = flowEngine,
)
@@ -124,7 +124,6 @@ fun WearApp(
handleGetNavigation(
navController = navController,
state = state,
- onCloseApp = onCloseApp,
selectEntry = selectEntry
)
}
@@ -147,7 +146,6 @@ fun WearApp(
private fun handleGetNavigation(
navController: NavController,
state: CredentialSelectorUiState.Get,
- onCloseApp: () -> Unit,
selectEntry: (entry: EntryInfo, isAutoSelected: Boolean) -> Unit,
) {
when (state) {
@@ -169,7 +167,7 @@ private fun handleGetNavigation(
}
}
- is SingleEntryPerAccount -> {
+ is MultipleEntryPrimaryScreen -> {
navController.navigateToMultipleCredentialsFoldScreen()
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 18c9f3102409..c641d7f9f48f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -32,7 +32,6 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.ChipColors
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.wear.compose.material.ChipDefaults
import com.android.credentialmanager.R
@@ -80,8 +79,7 @@ fun CredentialsScreenChip(
icon: Drawable? = null,
isAuthenticationEntryLocked: Boolean = false,
modifier: Modifier = Modifier,
- colors: ChipColors =
- ChipDefaults.chipColors(backgroundColor = colorResource(R.color.wear_material_almond)),
+ colors: ChipColors = ChipDefaults.primaryChipColors(),
) {
val labelParam: (@Composable RowScope.() -> Unit) =
{
@@ -168,11 +166,9 @@ fun ContinueChip(onClick: () -> Unit) {
WearButtonText(
text = stringResource(R.string.dialog_continue_button),
textAlign = TextAlign.Center,
- color = colorResource(R.color.wear_material_almond_dark),
)
},
- colors =
- ChipDefaults.chipColors(backgroundColor = colorResource(R.color.wear_material_almond)),
+ colors = ChipDefaults.primaryChipColors(),
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 437a699abcee..0afef5eba85e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -56,6 +56,6 @@ fun SignInHeader(
text = title,
)
- Spacer(modifier = Modifier.size(12.dp))
+ Spacer(modifier = Modifier.size(8.dp))
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
index e7a854f2a4d4..22f6bf0f37ee 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
@@ -26,7 +26,6 @@ import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
@Composable
@@ -34,7 +33,7 @@ fun WearTitleText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = WearMaterialTheme.colors.onSurface,
textAlign = TextAlign.Center,
style = WearMaterialTheme.typography.title3,
)
@@ -45,7 +44,7 @@ fun WearDisplayNameText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = WearMaterialTheme.colors.onSurfaceVariant,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
@@ -56,28 +55,30 @@ fun WearDisplayNameText(text: String, modifier: Modifier = Modifier) {
@Composable
fun WearUsernameText(
text: String,
+ textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
Text(
modifier = modifier.padding(start = 8.dp, end = 8.dp).wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
+ color = WearMaterialTheme.colors.onSurfaceVariant,
style = WearMaterialTheme.typography.caption1,
overflow = TextOverflow.Ellipsis,
- textAlign = TextAlign.Center,
+ textAlign = textAlign,
maxLines = 2,
onTextLayout = onTextLayout,
)
}
+// used for primary label in button
@Composable
fun WearButtonText(
text: String,
textAlign: TextAlign,
maxLines: Int = 1,
modifier: Modifier = Modifier,
- color: Color = LocalAndroidColorScheme.current.onSurface,
+ color: Color = WearMaterialTheme.colors.onSurface,
onTextLayout: (TextLayoutResult) -> Unit = {},
) {
Text(
@@ -101,8 +102,8 @@ fun WearSecondaryLabel(
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
- style = WearMaterialTheme.typography.button,
+ color = WearMaterialTheme.colors.onSurfaceVariant,
+ style = WearMaterialTheme.typography.caption1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
maxLines = 1,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 7a936b603ec1..04175335b9db 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.ui.mappers
+import android.graphics.drawable.Drawable
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
@@ -35,10 +36,19 @@ fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
entry = accounts[0].value.minWith(comparator)
)
} else {
- CredentialSelectorUiState.Get.SingleEntryPerAccount(
- sortedEntries = accounts.map {
- it.value.minWith(comparator)
- }.sortedWith(comparator),
+ val sortedEntries = accounts.map {
+ it.value.minWith(comparator)
+ }.sortedWith(comparator)
+
+ var icon: Drawable? = null
+ // provide icon if all entries have the same provider
+ if (sortedEntries.all {it.providerId == sortedEntries[0].providerId}) {
+ icon = providerInfos[0].icon
+ }
+
+ CredentialSelectorUiState.Get.MultipleEntryPrimaryScreen(
+ sortedEntries = sortedEntries,
+ icon = icon,
authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index a545e48eec0f..fb81e736171b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -27,7 +27,7 @@ import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
import com.android.credentialmanager.common.ui.components.WearButtonText
-import com.android.credentialmanager.common.ui.components.WearDisplayNameText
+import com.android.credentialmanager.common.ui.components.WearSecondaryLabel
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
import com.google.android.horologist.annotations.ExperimentalHorologistApi
@@ -64,10 +64,9 @@ fun MultiCredentialsFlattenScreen(
credentialSelectorUiState.accounts.forEach { userNameEntries ->
item {
- WearDisplayNameText(
+ WearSecondaryLabel(
text = userNameEntries.userName,
- modifier = Modifier.padding(top = 16.dp, bottom = 8.dp, start = 14.dp,
- end = 14.dp)
+ modifier = Modifier.padding(top = 12.dp, bottom = 4.dp)
)
}
@@ -86,9 +85,9 @@ fun MultiCredentialsFlattenScreen(
}
}
item {
- WearDisplayNameText(
+ WearSecondaryLabel(
text = stringResource(R.string.provider_list_title),
- modifier = Modifier.padding(top = 12.dp, bottom = 8.dp, start = 14.dp, end = 14.dp)
+ modifier = Modifier.padding(top = 12.dp, bottom = 4.dp)
)
}
credentialSelectorUiState.actionEntryList.forEach { actionEntry ->
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index acf4eca64c0b..7addc74aecd0 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -48,7 +48,7 @@ import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFoldScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntryPrimaryScreen,
columnState: ScalingLazyColumnState,
flowEngine: FlowEngine,
) {
@@ -61,29 +61,32 @@ fun MultiCredentialsFoldScreen(
val credentials = credentialSelectorUiState.sortedEntries
item {
var title = stringResource(R.string.choose_sign_in_title)
- if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
+
+ if (credentials.isEmpty()) {
+ title = stringResource(R.string.choose_sign_in_title)
+ } else if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
title = stringResource(R.string.choose_passkey_title)
} else if (credentials.all { it.credentialType == CredentialType.PASSWORD }) {
title = stringResource(R.string.choose_password_title)
}
SignInHeader(
- icon = null,
+ icon = credentialSelectorUiState.icon,
title = title,
)
}
credentials.forEach { credential: CredentialEntryInfo ->
- item {
- CredentialsScreenChip(
- label = credential.userName,
- onClick = { selectEntry(credential, false) },
- secondaryLabel = credential.credentialTypeDisplayName,
- icon = credential.icon,
- )
- CredentialsScreenChipSpacer()
- }
+ item {
+ CredentialsScreenChip(
+ label = credential.userName,
+ onClick = { selectEntry(credential, false) },
+ secondaryLabel = credential.credentialTypeDisplayName,
+ icon = credential.icon,
+ )
+ CredentialsScreenChipSpacer()
}
+ }
credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
item {
@@ -93,10 +96,13 @@ fun MultiCredentialsFoldScreen(
CredentialsScreenChipSpacer()
}
}
+
+ item {
+ Spacer(modifier = Modifier.size(8.dp))
+ }
+
item {
- Spacer(modifier = Modifier.size(12.dp))
SignInOptionsChip { flowEngine.openSecondaryScreen() }
- CredentialsScreenChipSpacer()
}
item {
DismissChip { flowEngine.cancel() }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index de7c1f19e193..03608a48beb6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -61,10 +61,18 @@ fun SinglePasskeyScreen(
)
},
accountContent = {
- AccountRow(
- primaryText = checkNotNull(entry.displayName),
+ val displayName = entry.displayName
+ if (displayName == null ||
+ entry.displayName.equals(entry.userName, ignoreCase = true)) {
+ AccountRow(
+ primaryText = entry.userName,
+ )
+ } else {
+ AccountRow(
+ primaryText = displayName,
secondaryText = entry.userName,
)
+ }
},
columnState = columnState,
modifier = Modifier.padding(horizontal = 10.dp)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index 884d9f6e5e16..34d6e977533e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -59,14 +59,15 @@ fun SignInWithProviderScreen(
},
accountContent = {
val displayName = entry.displayName
- if (displayName != null) {
+ if (displayName == null ||
+ entry.displayName.equals(entry.userName, ignoreCase = true)) {
AccountRow(
- primaryText = displayName,
- secondaryText = entry.userName,
+ primaryText = entry.userName,
)
} else {
AccountRow(
- primaryText = entry.userName,
+ primaryText = displayName,
+ secondaryText = entry.userName,
)
}
},
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialSelectorTheme.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialSelectorTheme.kt
new file mode 100644
index 000000000000..ee0ba7ba2274
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialSelectorTheme.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.credentialmanager.ui.theme
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import androidx.wear.compose.material.Colors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.wear.compose.material.Typography
+import androidx.wear.compose.material.MaterialTheme
+import com.android.credentialmanager.R
+import androidx.compose.ui.graphics.Color
+
+/** The Material 3 Theme Wrapper for Supporting RRO. */
+@Composable
+fun WearCredentialSelectorTheme(content: @Composable () -> Unit) {
+ val context = LocalContext.current
+ val colors =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ overlayColors(context)
+ .copy(error = MaterialTheme.colors.error, onError = MaterialTheme.colors.onError)
+ } else {
+ MaterialTheme.colors
+ }
+ MaterialTheme(colors = colors, typography = deviceDefaultTypography(context), content = content)
+}
+
+/**
+ * Creates a dynamic color maps that can be overlaid. 100 - Lightest shade; 0 - Darkest Shade; In
+ * wear we only support dark theme for the time being. Thus the fill colors and variants are dark
+ * and anything on top is light. We will use this custom redirection until wear compose material
+ * supports color scheming.
+ *
+ * The mapping is best case match on wear material color tokens from
+ * /android/clockwork/common/wearable/wearmaterial/color/res/values/color-tokens.xml
+ *
+ * @param context The context required to get system resource data.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+internal fun overlayColors(context: Context): Colors {
+ val tonalPalette = dynamicTonalPalette(context)
+ return Colors(
+ background = Color.Black,
+ onBackground = Color.White,
+ primary = tonalPalette.primary90,
+ primaryVariant = tonalPalette.primary80,
+ onPrimary = tonalPalette.primary10,
+ secondary = tonalPalette.tertiary90,
+ secondaryVariant = tonalPalette.tertiary60,
+ onSecondary = tonalPalette.tertiary10,
+ surface = tonalPalette.neutral20,
+ onSurface = tonalPalette.neutral95,
+ onSurfaceVariant = tonalPalette.neutralVariant80,
+ )
+}
+
+private fun fontFamily(context: Context, @StringRes id: Int): FontFamily {
+ val typefaceName = context.resources.getString(id)
+ val font = Font(familyName = DeviceFontFamilyName(typefaceName))
+ return FontFamily(font)
+}
+
+/*
+ Only customizes font family. The material 3 roles to 2.5 are mapped to the best case matching of
+ google3/java/com/google/android/wearable/libraries/compose/theme/GoogleMaterialTheme.kt
+*/
+internal fun deviceDefaultTypography(context: Context): Typography {
+ val defaultTypography = Typography()
+ return Typography(
+ display1 =
+ defaultTypography.display1.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_display_1_font_family)
+ ),
+ display2 =
+ defaultTypography.display2.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_display_2_font_family)
+ ),
+ display3 =
+ defaultTypography.display1.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_display_3_font_family)
+ ),
+ title1 =
+ defaultTypography.title1.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_title_1_font_family)
+ ),
+ title2 =
+ defaultTypography.title2.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_title_2_font_family)
+ ),
+ title3 =
+ defaultTypography.title3.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_title_3_font_family)
+ ),
+ body1 =
+ defaultTypography.body1.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_body_1_font_family)
+ ),
+ body2 =
+ defaultTypography.body2.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_body_2_font_family)
+ ),
+ button =
+ defaultTypography.button.copy(
+ fontFamily = fontFamily(context, R.string.wear_material_compose_button_font_family)
+ ),
+ caption1 =
+ defaultTypography.caption1.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_caption_1_font_family)
+ ),
+ caption2 =
+ defaultTypography.caption2.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_caption_2_font_family)
+ ),
+ caption3 =
+ defaultTypography.caption3.copy(
+ fontFamily =
+ fontFamily(context, R.string.wear_material_compose_caption_3_font_family)
+ ),
+ )
+} \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialTonalPalette.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialTonalPalette.kt
new file mode 100644
index 000000000000..1d6ed33e65e2
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/theme/WearCredentialTonalPalette.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.credentialmanager.ui.theme
+
+import android.R
+import android.content.Context
+import android.os.Build
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or tonal
+ * swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class WearCredentialSelectorTonalPalette(
+ // The neutral tonal range.
+ val neutral100: Color,
+ val neutral99: Color,
+ val neutral95: Color,
+ val neutral90: Color,
+ val neutral80: Color,
+ val neutral70: Color,
+ val neutral60: Color,
+ val neutral50: Color,
+ val neutral40: Color,
+ val neutral30: Color,
+ val neutral20: Color,
+ val neutral10: Color,
+ val neutral0: Color,
+
+ // The neutral variant tonal range, sometimes called "neutral 2"
+ val neutralVariant100: Color,
+ val neutralVariant99: Color,
+ val neutralVariant95: Color,
+ val neutralVariant90: Color,
+ val neutralVariant80: Color,
+ val neutralVariant70: Color,
+ val neutralVariant60: Color,
+ val neutralVariant50: Color,
+ val neutralVariant40: Color,
+ val neutralVariant30: Color,
+ val neutralVariant20: Color,
+ val neutralVariant10: Color,
+ val neutralVariant0: Color,
+
+ // The primary tonal range, also known as accent 1
+ val primary100: Color,
+ val primary99: Color,
+ val primary95: Color,
+ val primary90: Color,
+ val primary80: Color,
+ val primary70: Color,
+ val primary60: Color,
+ val primary50: Color,
+ val primary40: Color,
+ val primary30: Color,
+ val primary20: Color,
+ val primary10: Color,
+ val primary0: Color,
+
+ // The Secondary tonal range, also know as accent 2
+ val secondary100: Color,
+ val secondary99: Color,
+ val secondary95: Color,
+ val secondary90: Color,
+ val secondary80: Color,
+ val secondary70: Color,
+ val secondary60: Color,
+ val secondary50: Color,
+ val secondary40: Color,
+ val secondary30: Color,
+ val secondary20: Color,
+ val secondary10: Color,
+ val secondary0: Color,
+
+ // The tertiary tonal range, also known as accent 3
+ val tertiary100: Color,
+ val tertiary99: Color,
+ val tertiary95: Color,
+ val tertiary90: Color,
+ val tertiary80: Color,
+ val tertiary70: Color,
+ val tertiary60: Color,
+ val tertiary50: Color,
+ val tertiary40: Color,
+ val tertiary30: Color,
+ val tertiary20: Color,
+ val tertiary10: Color,
+ val tertiary0: Color,
+)
+/** Dynamic colors for wear compose material to support resource overlay. */
+@RequiresApi(Build.VERSION_CODES.S)
+// TODO: once we have proper support for this on Wear 6+, we will do something similar to
+// https://source.corp.google.com/h/android/platform/superproject/+/androidx-main:frameworks/support/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.android.kt;l=307-362?q=dynamicTonalPalette&sq=repo:android%2Fplatform%2Fsuperproject%20b:androidx-main
+// Tracking Bug: b/270720571
+internal fun dynamicTonalPalette(context: Context) =
+ WearCredentialSelectorTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
+ neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
+ neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
+ neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
+ neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
+ neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
+ neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
+ neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
+ neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
+ neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
+ neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
+ neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
+ neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
+ neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
+ neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
+ neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
+ neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
+ neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
+ neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
+ neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
+ neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
+ neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
+ neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
+ neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
+ neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
+
+ // The primary tonal range from the generated dynamic color palette.
+ primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
+ primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
+ primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
+ primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
+ primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
+ primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
+ primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
+ primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
+ primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
+ primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
+ primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
+ primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
+ primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
+
+ // The secondary tonal range from the generated dynamic color palette.
+ secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
+ secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
+ secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
+ secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
+ secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
+ secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
+ secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
+ secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
+ secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
+ secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
+ secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
+ secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
+ secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
+ tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
+ tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
+ tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
+ tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
+ tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
+ tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
+ tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
+ tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
+ tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
+ tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
+ tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
+ tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
+ )
+
+private object ColorResourceHelper {
+ @DoNotInline
+ fun getColor(context: Context, @ColorRes id: Int): Color {
+ return Color(context.resources.getColor(id, context.theme))
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4640de304ed8..fbbed921de1d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1673,6 +1673,8 @@
<string name="accessibility_phone_two_bars">Phone two bars.</string>
<!-- Content description of the phone signal when it is three bars for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_phone_three_bars">Phone three bars.</string>
+ <!-- Content description of the phone signal when it is four bars for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_phone_four_bars">Phone four bars.</string>
<!-- Content description of the phone signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_phone_signal_full">Phone signal full.</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index ce466dfbf19c..9073d281c4a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -33,6 +33,59 @@ public class AccessibilityContentDescriptions {
R.string.accessibility_phone_signal_full
};
+ /**
+ * @param level int in range [0-4] that describes the signal level
+ * @return the appropriate content description for that signal strength, or 0 if the param is
+ * invalid
+ */
+ public static int getDescriptionForLevel(int level) {
+ if (level > 4 || level < 0) {
+ return 0;
+ }
+
+ return PHONE_SIGNAL_STRENGTH[level];
+ }
+
+ public static final int[] PHONE_SIGNAL_STRENGTH_INFLATED = {
+ PHONE_SIGNAL_STRENGTH_NONE,
+ R.string.accessibility_phone_one_bar,
+ R.string.accessibility_phone_two_bars,
+ R.string.accessibility_phone_three_bars,
+ R.string.accessibility_phone_four_bars,
+ R.string.accessibility_phone_signal_full
+ };
+
+ /**
+ * @param level int in range [0-5] that describes the inflated signal level
+ * @return the appropriate content description for that signal strength, or 0 if the param is
+ * invalid
+ */
+ public static int getDescriptionForInflatedLevel(int level) {
+ if (level > 5 || level < 0) {
+ return 0;
+ }
+
+ return PHONE_SIGNAL_STRENGTH_INFLATED[level];
+ }
+
+ /**
+ * @param level int in range [0-5] that describes the inflated signal level
+ * @param numberOfLevels one of (4, 5) that describes the default number of levels, or the
+ * inflated number of levels. The level param should be relative to the
+ * number of levels. This won't do any inflation.
+ * @return the appropriate content description for that signal strength, or 0 if the param is
+ * invalid
+ */
+ public static int getDescriptionForLevel(int level, int numberOfLevels) {
+ if (numberOfLevels == 5) {
+ return getDescriptionForLevel(level);
+ } else if (numberOfLevels == 6) {
+ return getDescriptionForInflatedLevel(level);
+ } else {
+ return 0;
+ }
+ }
+
public static final int[] DATA_CONNECTION_STRENGTH = {
R.string.accessibility_no_data,
R.string.accessibility_data_one_bar,
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8137e408ba39..14ebc3907c04 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -32,6 +32,16 @@ flag {
}
flag {
+ name: "floating_menu_narrow_target_content_observer"
+ namespace: "accessibility"
+ description: "stops the FAB from monitoring enabled services to trigger target content changes."
+ bug: "331740049"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "floating_menu_overlaps_nav_bars_flag"
namespace: "accessibility"
description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index b105a4e3b05a..c979d053617a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -477,10 +477,13 @@ flag {
}
flag {
- name: "screenshot_private_profile"
+ name: "screenshot_private_profile_behavior_fix"
namespace: "systemui"
description: "Private profile support for screenshots"
bug: "327613051"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -770,4 +773,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "dream_input_session_pilfer_once"
+ namespace: "systemui"
+ description: "Pilfer at most once per input session"
+ bug: "324600132"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index fc511e12ec54..e15d315f9a0a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -69,9 +69,9 @@ class ButtonComponent(
role = Role.Button
contentDescription = label
},
- color = MaterialTheme.colorScheme.primaryContainer,
+ color = MaterialTheme.colorScheme.tertiaryContainer,
shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
onClick = onClick,
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 780e3f2de4c8..b2351c492fc1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -66,8 +66,8 @@ class ToggleButtonComponent(
val colors =
if (viewModel.isChecked) {
ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
)
} else {
ButtonDefaults.buttonColors(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index c74331477229..51e206470389 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -119,6 +121,7 @@ fun VolumePanelRadioButtonBar(
) {
for (itemIndex in items.indices) {
val item = items[itemIndex]
+ val isSelected = itemIndex == scope.selectedIndex
Row(
modifier =
Modifier.height(48.dp)
@@ -126,7 +129,7 @@ fun VolumePanelRadioButtonBar(
.semantics {
item.contentDescription?.let { contentDescription = it }
role = Role.Switch
- selected = itemIndex == scope.selectedIndex
+ selected = isSelected
}
.clickable(
interactionSource = null,
@@ -137,7 +140,11 @@ fun VolumePanelRadioButtonBar(
verticalAlignment = Alignment.CenterVertically,
) {
if (item.icon !== Empty) {
- with(items[itemIndex]) { icon() }
+ CompositionLocalProvider(
+ LocalContentColor provides colors.getIconColor(isSelected)
+ ) {
+ with(items[itemIndex]) { icon() }
+ }
}
}
}
@@ -163,7 +170,10 @@ fun VolumePanelRadioButtonBar(
) {
val item = items[itemIndex]
if (item.icon !== Empty) {
- with(items[itemIndex]) { label() }
+ val textColor = colors.getLabelColor(itemIndex == scope.selectedIndex)
+ CompositionLocalProvider(LocalContentColor provides textColor) {
+ with(items[itemIndex]) { label() }
+ }
}
}
}
@@ -265,8 +275,22 @@ data class VolumePanelRadioButtonBarColors(
val indicatorColor: Color,
/** Color of the indicator background. */
val indicatorBackgroundColor: Color,
+ /** Color of the icon. */
+ val iconColor: Color,
+ /** Color of the icon when it's selected. */
+ val selectedIconColor: Color,
+ /** Color of the label. */
+ val labelColor: Color,
+ /** Color of the label when it's selected. */
+ val selectedLabelColor: Color,
)
+private fun VolumePanelRadioButtonBarColors.getIconColor(selected: Boolean): Color =
+ if (selected) selectedIconColor else iconColor
+
+private fun VolumePanelRadioButtonBarColors.getLabelColor(selected: Boolean): Color =
+ if (selected) selectedLabelColor else labelColor
+
object VolumePanelRadioButtonBarDefaults {
val DefaultIndicatorBackgroundPadding = 8.dp
@@ -283,12 +307,20 @@ object VolumePanelRadioButtonBarDefaults {
*/
@Composable
fun defaultColors(
- indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+ indicatorColor: Color = MaterialTheme.colorScheme.tertiaryContainer,
indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ iconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedIconColor: Color = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedLabelColor: Color = MaterialTheme.colorScheme.onSurface,
): VolumePanelRadioButtonBarColors =
VolumePanelRadioButtonBarColors(
indicatorColor = indicatorColor,
indicatorBackgroundColor = indicatorBackgroundColor,
+ iconColor = iconColor,
+ selectedIconColor = selectedIconColor,
+ labelColor = labelColor,
+ selectedLabelColor = selectedLabelColor,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 9a98bdeec8f1..f377fa6276a0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
import androidx.compose.foundation.basicMarquee
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -29,7 +30,6 @@ import androidx.compose.ui.text.style.TextAlign
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.toColor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
@@ -88,18 +88,13 @@ constructor(
isSelected = buttonViewModel.button.isChecked,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
contentDescription = label,
- icon = {
- Icon(
- icon = buttonViewModel.button.icon,
- tint = buttonViewModel.iconColor.toColor(),
- )
- },
+ icon = { Icon(icon = buttonViewModel.button.icon) },
label = {
Text(
modifier = Modifier.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- color = buttonViewModel.labelColor.toColor(),
+ color = LocalContentColor.current,
textAlign = TextAlign.Center,
maxLines = 2
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index a54d005c990a..a3467f2ab78e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -107,7 +107,7 @@ fun ColumnVolumeSliders(
}
}
transition.AnimatedVisibility(
- visible = { it },
+ visible = { it || !isExpandable },
enter =
expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
exit =
@@ -122,7 +122,7 @@ fun ColumnVolumeSliders(
val sliderState by sliderViewModel.slider.collectAsState()
transition.AnimatedVisibility(
modifier = Modifier.padding(top = 16.dp),
- visible = { it },
+ visible = { it || !isExpandable },
enter = enterTransition(index = index, totalCount = viewModels.size),
exit = exitTransition(index = index, totalCount = viewModels.size)
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 228d29259038..9f5ab3c0e284 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -61,7 +61,8 @@ fun VolumeSlider(
modifier =
modifier.clearAndSetSemantics {
if (!state.isEnabled) disabled()
- contentDescription = state.label
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
// provide a not animated value to the a11y because it fails to announce the
// settled value when it changes rapidly.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 5bb36a0acbdf..256687b56f4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -389,6 +389,61 @@ class PinBouncerViewModelTest : SysuiTestCase() {
assertThat(isAnimationEnabled).isTrue()
}
+ @Test
+ fun onPinButtonClicked_whenInputSameLengthAsHintedPin_ignoresClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isEqualTo(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH - 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+ kosmos.fakeAuthenticationRepository.pauseCredentialChecking()
+ // If credential checking were not paused, this would check the credentials and succeed.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ runCurrent()
+
+ // This one should be ignored because the user has already entered a number of digits
+ // that's equal to the length of the hinting PIN length. It should result in a PIN
+ // that's exactly the same length as the hinting PIN length.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ runCurrent()
+
+ assertThat(pin)
+ .isEqualTo(
+ buildList {
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH) { index ->
+ add(index + 1)
+ }
+ }
+ )
+
+ kosmos.fakeAuthenticationRepository.unpauseCredentialChecking()
+ runCurrent()
+ assertThat(pin).isEmpty()
+ }
+
+ @Test
+ fun onPinButtonClicked_whenPinNotHinted_doesNotIgnoreClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isNull()
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+
+ assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
new file mode 100644
index 000000000000..63600beff126
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="@color/overlay_button_ripple">
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSecondary"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
new file mode 100644
index 000000000000..bb8cece9203b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
new file mode 100644
index 000000000000..a5b44e564157
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android = "http://schemas.android.com/apk/res/android">
+ <size
+ android:width = "@dimen/overlay_action_chip_margin_start"
+ android:height = "0dp"/>
+</shape>
diff --git a/packages/CredentialManager/wear/res/values/colors.xml b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
index bf10bb3d7178..76779f9f1b2c 100644
--- a/packages/CredentialManager/wear/res/values/colors.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
@@ -14,8 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<resources>
- <color name="wear_material_almond">#FFFCF7EB</color>
- <color name="wear_material_almond_dark">#FF262523</color>
-</resources> \ No newline at end of file
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <!-- We don't actually draw anything, just expressing the shape for clipping. -->
+ <solid android:color="#0000"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 13355f374dd6..76d10ccb8a25 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -47,7 +47,7 @@
android:layout_marginBottom="@dimen/bluetooth_dialog_layout_margin"
android:ellipsize="end"
android:gravity="center_vertical|center_horizontal"
- android:maxLines="1"
+ android:maxLines="2"
android:text="@string/quick_settings_bluetooth_tile_subtitle"
android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
app:layout_constraintEnd_toEndOf="parent"
@@ -256,6 +256,24 @@
app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />
<Button
+ android:id="@+id/audio_sharing_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="9dp"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/barrier"
+ app:layout_constraintVertical_bias="1"
+ android:visibility="gone" />
+
+ <Button
android:id="@+id/done_button"
style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 1eb05bfd602d..e3c5a7d03d2e 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -36,8 +36,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
- android:focusable="true"
- android:accessibilityTraversalBefore="@android:id/edit"
+ android:focusable="false"
+ android:importantForAccessibility="yes"
android:clipToPadding="false"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index eeb64bd8460e..6a5b999f5444 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,39 +20,37 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <ImageView
+ <FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/guideline"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_width="wrap_content"
android:elevation="4dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
- <LinearLayout
- android:id="@+id/screenshot_actions"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ >
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </HorizontalScrollView>
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+ android:background="@drawable/shelf_action_container_clipping_shape"
+ android:clipToOutline="true"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
+ android:animateLayoutChanges="true"
+ />
+ </HorizontalScrollView>
+ </FrameLayout>
<View
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
@@ -66,7 +64,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/actions_container"/>
+ app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
<ImageView
android:id="@+id/screenshot_preview"
android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/layout/shelf_action_chip.xml b/packages/SystemUI/res/layout/shelf_action_chip.xml
new file mode 100644
index 000000000000..709c80d07088
--- /dev/null
+++ b/packages/SystemUI/res/layout/shelf_action_chip.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.systemui.screenshot.OverlayActionChip
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/overlay_action_chip"
+ android:theme="@style/FloatingOverlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:alpha="0.0">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
+ android:background="@drawable/shelf_action_chip_background"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/overlay_action_chip_icon"
+ android:tint="?androidprv:attr/materialColorOnSecondary"
+ android:layout_width="@dimen/overlay_action_chip_icon_size"
+ android:layout_height="@dimen/overlay_action_chip_icon_size"/>
+ <TextView
+ android:id="@+id/overlay_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="@dimen/overlay_action_chip_text_size"
+ android:textColor="?androidprv:attr/materialColorOnSecondary"/>
+ </LinearLayout>
+</com.android.systemui.screenshot.OverlayActionChip>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index af661aa172c7..f60f6c7af289 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -678,6 +678,8 @@
<string name="turn_on_bluetooth">Use Bluetooth</string>
<!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_connected">Connected</string>
+ <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
<!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_saved">Saved</string>
<!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -687,9 +689,13 @@
<!-- QuickSettings: Bluetooth auto on tomorrow [CHAR LIMIT=NONE]-->
<string name="turn_on_bluetooth_auto_tomorrow">Automatically turn on again tomorrow</string>
<!-- QuickSettings: Bluetooth auto on info text when disabled [CHAR LIMIT=NONE]-->
- <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share, Find My Device, and device location use Bluetooth</string>
+ <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
<!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
- <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow at 5 AM</string>
+ <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
<!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
index a2b119833474..f07dce5e3482 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.shared.model
+import android.graphics.Rect
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
/** Fingerprint sensor property. Represents [FingerprintSensorPropertiesInternal]. */
@@ -23,12 +24,23 @@ data class FingerprintSensor(
val sensorId: Int,
val sensorStrength: SensorStrength,
val maxEnrollmentsPerUser: Int,
- val sensorType: FingerprintSensorType
+ val sensorType: FingerprintSensorType,
+ val sensorBounds: Rect,
+ val sensorRadius: Int,
)
/** Convert [FingerprintSensorPropertiesInternal] to corresponding [FingerprintSensor] */
fun FingerprintSensorPropertiesInternal.toFingerprintSensor(): FingerprintSensor {
val sensorStrength: SensorStrength = this.sensorStrength.toSensorStrength()
val sensorType: FingerprintSensorType = this.sensorType.toSensorType()
- return FingerprintSensor(this.sensorId, sensorStrength, this.maxEnrollmentsPerUser, sensorType)
+ val sensorBounds: Rect = this.location.rect
+ val sensorRadius = this.location.sensorRadius
+ return FingerprintSensor(
+ this.sensorId,
+ sensorStrength,
+ this.maxEnrollmentsPerUser,
+ sensorType,
+ sensorBounds,
+ sensorRadius,
+ )
}
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 476daac5ff00..0f3d28586588 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -33,3 +33,10 @@ fun Int.toSensorStrength(): SensorStrength =
SensorProperties.STRENGTH_STRONG -> SensorStrength.STRONG
else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
}
+/** Convert [SensorStrength] to corresponding [Int] */
+fun SensorStrength.toInt(): Int =
+ when (this) {
+ SensorStrength.CONVENIENCE -> SensorProperties.STRENGTH_CONVENIENCE
+ SensorStrength.WEAK -> SensorProperties.STRENGTH_WEAK
+ SensorStrength.STRONG -> SensorProperties.STRENGTH_STRONG
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 7e96e48545ea..615363da073a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -72,6 +72,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
private boolean mEndAnimationCanceled = false;
@MagnificationState
private int mState = STATE_DISABLED;
+ private Runnable mOnAnimationEndRunnable;
WindowMagnificationAnimationController(@UiContext Context context) {
this(context, newValueAnimator(context.getResources()));
@@ -303,12 +304,7 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
return;
}
- // If the animation is playing backwards, mStartSpec will be the final spec we would
- // like to reach.
- AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
- mController.updateWindowMagnificationInternal(
- spec.mScale, spec.mCenterX, spec.mCenterY,
- mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+ mOnAnimationEndRunnable.run();
if (mState == STATE_DISABLING) {
mController.deleteWindowMagnification();
@@ -333,6 +329,10 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
public void onAnimationRepeat(Animator animation) {
}
+ void setOnAnimationEndRunnable(Runnable runnable) {
+ mOnAnimationEndRunnable = runnable;
+ }
+
private void sendAnimationCallback(boolean success) {
if (mAnimationCallback != null) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a847c3d510b1..9837e369bc91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -260,6 +260,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mContext = context;
mHandler = handler;
mAnimationController = animationController;
+ mAnimationController.setOnAnimationEndRunnable(() -> {
+ if (Flags.createWindowlessWindowMagnifier()) {
+ notifySourceBoundsChanged();
+ }
+ });
mAnimationController.setWindowMagnificationController(this);
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
@@ -1051,11 +1056,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
// Notify source bounds change when the magnifier is not animating.
if (!mAnimationController.isAnimating()) {
- mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ notifySourceBoundsChanged();
}
}
}
+ private void notifySourceBoundsChanged() {
+ mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ }
+
/**
* Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based
* on the position and size of {@link #mMagnificationFrame}.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 1018f70c7f60..eb840f1f4c90 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -244,11 +244,13 @@ class MenuInfoRepository {
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
- /* notifyForDescendants */ false,
- mMenuTargetFeaturesContentObserver,
- UserHandle.USER_CURRENT);
+ if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
+ /* notifyForDescendants */ false,
+ mMenuTargetFeaturesContentObserver,
+ UserHandle.USER_CURRENT);
+ }
mSecureSettings.registerContentObserverForUser(
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants */ false, mMenuSizeContentObserver,
@@ -263,8 +265,10 @@ class MenuInfoRepository {
UserHandle.USER_CURRENT);
mContext.registerComponentCallbacks(mComponentCallbacks);
- mAccessibilityManager.addAccessibilityServicesStateChangeListener(
- mA11yServicesStateChangeListener);
+ if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
+ }
}
void unregisterObserversAndCallbacks() {
@@ -273,8 +277,10 @@ class MenuInfoRepository {
mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
mContext.unregisterComponentCallbacks(mComponentCallbacks);
- mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
- mA11yServicesStateChangeListener);
+ if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+ mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
+ }
}
interface OnSettingsContentsChanged {
diff --git a/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
new file mode 100644
index 000000000000..b65d29c6a0bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ailabs/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 1495344
+
+dupin@google.com
+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/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
new file mode 100644
index 000000000000..e44f0543fc87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+internal sealed class AudioSharingButtonState {
+ object Gone : AudioSharingButtonState()
+ data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
+}
+
+/** Holds business logic for the audio sharing state. */
+@SysUISingleton
+internal class AudioSharingInteractor
+@Inject
+constructor(
+ private val localBluetoothManager: LocalBluetoothManager?,
+ bluetoothStateInteractor: BluetoothStateInteractor,
+ deviceItemInteractor: DeviceItemInteractor,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ /** Flow representing the update of AudioSharingButtonState. */
+ internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
+ combine(
+ bluetoothStateInteractor.bluetoothStateUpdate,
+ deviceItemInteractor.deviceItemUpdate
+ ) { bluetoothState, deviceItem ->
+ getButtonState(bluetoothState, deviceItem)
+ }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+ initialValue = AudioSharingButtonState.Gone
+ )
+
+ private fun getButtonState(
+ bluetoothState: Boolean,
+ deviceItem: List<DeviceItem>
+ ): AudioSharingButtonState {
+ return when {
+ // Don't show button when bluetooth is off
+ !bluetoothState -> AudioSharingButtonState.Gone
+ // Show sharing audio when broadcasting
+ BluetoothUtils.isBroadcasting(localBluetoothManager) ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ // When not broadcasting, don't show button if there's connected source in any device
+ deviceItem.any {
+ BluetoothUtils.hasConnectedBroadcastSource(
+ it.cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ } -> AudioSharingButtonState.Gone
+ // Show audio sharing when there's a connected LE audio device
+ deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ else -> AudioSharingButtonState.Gone
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 94d7af74f1dd..17f9e634ec62 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -25,12 +25,17 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
@@ -40,9 +45,10 @@ constructor(
private val localBluetoothManager: LocalBluetoothManager?,
private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- internal val bluetoothStateUpdate: StateFlow<Boolean?> =
+ internal val bluetoothStateUpdate: StateFlow<Boolean> =
conflatedCallbackFlow {
val listener =
object : BluetoothCallback {
@@ -64,16 +70,22 @@ constructor(
localBluetoothManager?.eventManager?.registerCallback(listener)
awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
}
+ .onStart { emit(isBluetoothEnabled()) }
+ .flowOn(backgroundDispatcher)
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
- initialValue = null
+ initialValue = false
)
- internal var isBluetoothEnabled: Boolean
- get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
- set(value) {
- if (isBluetoothEnabled != value) {
+ suspend fun isBluetoothEnabled(): Boolean =
+ withContext(backgroundDispatcher) {
+ localBluetoothManager?.bluetoothAdapter?.isEnabled == true
+ }
+
+ suspend fun setBluetoothEnabled(value: Boolean) {
+ withContext(backgroundDispatcher) {
+ if (isBluetoothEnabled() != value) {
localBluetoothManager?.bluetoothAdapter?.apply {
if (value) enable() else disable()
logger.logBluetoothState(
@@ -83,6 +95,7 @@ constructor(
}
}
}
+ }
companion object {
private const val TAG = "BtStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index c7d171d5b804..dd8c0df387dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -27,6 +27,7 @@ import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Switch
@@ -59,7 +60,6 @@ class BluetoothTileDialogDelegate
internal constructor(
@Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
@Assisted private val cachedContentHeight: Int,
- @Assisted private val bluetoothToggleInitialValue: Boolean,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val dismissListener: Runnable,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@ internal constructor(
private val systemuiDialogFactory: SystemUIDialog.Factory,
) : SystemUIDialog.Delegate {
- private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
- MutableStateFlow(bluetoothToggleInitialValue)
+ private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
internal val bluetoothStateToggle
get() = mutableBluetoothStateToggle.asStateFlow()
@@ -99,7 +98,6 @@ internal constructor(
fun create(
initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
cachedContentHeight: Int,
- bluetoothEnabled: Boolean,
dialogCallback: BluetoothTileDialogCallback,
dimissListener: Runnable
): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@ internal constructor(
getPairNewDeviceButton(dialog).setOnClickListener {
bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
}
+ getAudioSharingButtonView(dialog).setOnClickListener {
+ bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+ }
getScrollViewContent(dialog).apply {
minimumHeight =
resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@ internal constructor(
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
}
+ internal fun onAudioSharingButtonUpdated(
+ dialog: SystemUIDialog,
+ visibility: Int,
+ label: String?
+ ) {
+ getAudioSharingButtonView(dialog).apply {
+ this.visibility = visibility
+ label?.let { text = it }
+ }
+ }
+
private fun setupToggle(dialog: SystemUIDialog) {
val toggleView = getToggleView(dialog)
- toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothStateToggle.value = isChecked
view.apply {
@@ -259,6 +270,10 @@ internal constructor(
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
}
+ private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
+ return dialog.requireViewById(R.id.audio_sharing_button)
+ }
+
private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
}
@@ -412,6 +427,8 @@ internal constructor(
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+ const val ACTION_AUDIO_SHARING =
+ "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index add1647143d8..b592b8ed4332 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -30,9 +30,12 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
@UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
@UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
@UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+ @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
@UiEvent(doc = "Connected other device clicked to disconnect")
CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
- @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617);
+ @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
+ @UiEvent(doc = "The audio sharing button is clicked")
+ BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index e65b65710f94..eb919e3ca36b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,9 +28,11 @@ import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
@@ -61,6 +63,7 @@ constructor(
private val deviceItemInteractor: DeviceItemInteractor,
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
+ private val audioSharingInteractor: AudioSharingInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
private val uiEventLogger: UiEventLogger,
@@ -119,7 +122,8 @@ constructor(
dialog,
it.take(MAX_DEVICE_ITEM_ENTRY),
showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
- showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ showPairNewDevice =
+ bluetoothStateInteractor.isBluetoothEnabled()
)
animateProgressBar(dialog, false)
}
@@ -142,10 +146,25 @@ constructor(
}
.launchIn(this)
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ audioSharingInteractor.audioSharingButtonStateUpdate
+ .onEach {
+ if (it is AudioSharingButtonState.Visible) {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ VISIBLE,
+ context.getString(it.resId)
+ )
+ } else {
+ dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null)
+ }
+ }
+ .launchIn(this)
+ }
+
// bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
// the device item list.
bluetoothStateInteractor.bluetoothStateUpdate
- .filterNotNull()
.onEach {
dialogDelegate.onBluetoothStateUpdated(
dialog,
@@ -165,9 +184,10 @@ constructor(
// bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
// send the new value to the bluetoothStateInteractor and animate the progress bar.
dialogDelegate.bluetoothStateToggle
+ .filterNotNull()
.onEach {
dialogDelegate.animateProgressBar(dialog, true)
- bluetoothStateInteractor.isBluetoothEnabled = it
+ bluetoothStateInteractor.setBluetoothEnabled(it)
}
.launchIn(this)
@@ -222,11 +242,10 @@ constructor(
return bluetoothDialogDelegateFactory.create(
UiProperties.build(
- bluetoothStateInteractor.isBluetoothEnabled,
+ bluetoothStateInteractor.isBluetoothEnabled(),
isAutoOnToggleFeatureAvailable()
),
cachedContentHeight,
- bluetoothStateInteractor.isBluetoothEnabled,
this@BluetoothTileDialogViewModel,
{ cancelJob() }
)
@@ -256,6 +275,11 @@ constructor(
startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
}
+ override fun onAudioSharingButtonClicked(view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
+ startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+ }
+
private fun cancelJob() {
job?.cancel()
job = null
@@ -312,4 +336,5 @@ interface BluetoothTileDialogCallback {
fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
+ fun onAudioSharingButtonClicked(view: View)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index dc5efefdfb16..0ea98d14bca3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -37,6 +37,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice
enum class DeviceItemType {
ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
CONNECTED_BLUETOOTH_DEVICE,
SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index f04ba75ca3ef..49d0847ab0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,13 +21,16 @@ import android.content.Context
import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
private val connected = R.string.quick_settings_bluetooth_device_connected
+private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
private val saved = R.string.quick_settings_bluetooth_device_saved
private val actionAccessibilityLabelActivate =
R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -39,35 +42,81 @@ internal abstract class DeviceItemFactory {
abstract fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager,
): Boolean
abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
+
+ companion object {
+ @JvmStatic
+ fun createDeviceItem(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ type: DeviceItemType,
+ connectionSummary: String,
+ background: Int,
+ actionAccessibilityLabel: String
+ ): DeviceItem {
+ return DeviceItem(
+ type = type,
+ cachedBluetoothDevice = cachedDevice,
+ deviceName = cachedDevice.name,
+ connectionSummary = connectionSummary,
+ iconWithDescription =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
+ Pair(it.first, it.second)
+ },
+ background = background,
+ isEnabled = !cachedDevice.isBusy,
+ actionAccessibilityLabel = actionAccessibilityLabel
+ )
+ }
+ }
}
internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary ?: "",
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = backgroundOn,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary ?: "",
+ backgroundOn,
+ context.getString(actionAccessibilityLabelDisconnect)
+ )
+ }
+}
+
+internal class AudioSharingMediaDeviceItemFactory(
+ private val localBluetoothManager: LocalBluetoothManager?
+) : DeviceItemFactory() {
+ override fun isFilterMatched(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ audioManager: AudioManager
+ ): Boolean {
+ return enableLeAudioSharing() &&
+ BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+ }
+
+ override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(audioSharing),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
+ ""
)
}
}
@@ -76,7 +125,7 @@ internal class ActiveHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -87,27 +136,21 @@ internal open class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
- // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -116,7 +159,7 @@ internal class AvailableHearingDeviceItemFactory : ActiveMediaDeviceItemFactory(
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -127,32 +170,25 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
} else {
BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelDisconnect)
)
}
}
@@ -161,32 +197,26 @@ internal open class SavedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ !cachedDevice.isConnected
} else {
cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(saved),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(saved),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -195,7 +225,7 @@ internal class SavedHearingDeviceItemFactory : SavedDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 4e28cafb5004..66e593b94b21 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -113,6 +113,7 @@ constructor(
private var deviceItemFactoryList: List<DeviceItemFactory> =
listOf(
ActiveMediaDeviceItemFactory(),
+ AudioSharingMediaDeviceItemFactory(localBluetoothManager),
AvailableMediaDeviceItemFactory(),
ConnectedDeviceItemFactory(),
SavedDeviceItemFactory()
@@ -121,6 +122,7 @@ constructor(
private var displayPriority: List<DeviceItemType> =
listOf(
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
DeviceItemType.SAVED_BLUETOOTH_DEVICE,
@@ -177,6 +179,9 @@ constructor(
disconnect()
uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
}
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+ }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 12cac9251b25..4c2380c5e4db 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -135,8 +135,11 @@ class PinBouncerViewModel(
onIntentionalUserInput()
- mutablePinInput.value = pinInput.append(input)
- tryAuthenticate(useAutoConfirm = true)
+ val maxInputLength = hintedPinLength.value ?: Int.MAX_VALUE
+ if (pinInput.getPin().size < maxInputLength) {
+ mutablePinInput.value = pinInput.append(input)
+ tryAuthenticate(useAutoConfirm = true)
+ }
}
/** Notifies that the user clicked the backspace button. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
index e1d03392044a..cddba04b5a7c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.touch;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
import android.os.Looper;
@@ -24,7 +23,8 @@ import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.Flags;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -42,26 +42,34 @@ public class InputSession {
private final InputChannelCompat.InputEventReceiver mInputEventReceiver;
private final GestureDetector mGestureDetector;
+ // Pilfering is a destructive operation. Once pilfering starts, the all events will be captured
+ // by the associated monitor. We track whether we're pilfering since initiating pilfering
+ // requires reaching out to the InputManagerService, which can be a heavy operation. This is
+ // especially costly if this is happening on a continuous stream of motion events.
+ private boolean mPilfering;
+
/**
* Default session constructor.
- * @param sessionName The session name that will be applied to the underlying
- * {@link InputMonitorCompat}.
+ * @param inputMonitor Input monitor to track input events.
+ * @param gestureDetector Gesture detector for detecting gestures.
* @param inputEventListener A listener to receive input events.
- * @param gestureListener A listener to receive gesture events.
+ * @param choreographer Choreographer to use with the input receiver.
+ * @param looper Looper to use with the input receiver
* @param pilferOnGestureConsume Whether touch events should be pilfered after a gesture has
* been consumed.
*/
@Inject
- public InputSession(@Named(INPUT_SESSION_NAME) String sessionName,
+ public InputSession(
+ InputMonitorCompat inputMonitor,
+ GestureDetector gestureDetector,
InputChannelCompat.InputEventListener inputEventListener,
- GestureDetector.OnGestureListener gestureListener,
- DisplayTracker displayTracker,
+ Choreographer choreographer,
+ @Main Looper looper,
@Named(PILFER_ON_GESTURE_CONSUME) boolean pilferOnGestureConsume) {
- mInputMonitor = new InputMonitorCompat(sessionName, displayTracker.getDefaultDisplayId());
- mGestureDetector = new GestureDetector(gestureListener);
+ mInputMonitor = inputMonitor;
+ mGestureDetector = gestureDetector;
- mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
- Choreographer.getInstance(),
+ mInputEventReceiver = mInputMonitor.getInputReceiver(looper, choreographer,
ev -> {
// Process event. Since sometimes input may be a prerequisite for some
// gesture logic, process input first.
@@ -69,7 +77,9 @@ public class InputSession {
if (ev instanceof MotionEvent
&& mGestureDetector.onTouchEvent((MotionEvent) ev)
- && pilferOnGestureConsume) {
+ && pilferOnGestureConsume
+ && !(mPilfering && Flags.dreamInputSessionPilferOnce())) {
+ mPilfering = true;
mInputMonitor.pilferPointers();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
index ad59a2e2b5c3..0b145211cd45 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
@@ -24,16 +24,18 @@ import android.view.GestureDetector;
import com.android.systemui.dreams.touch.InputSession;
import com.android.systemui.shared.system.InputChannelCompat;
-import javax.inject.Named;
-
import dagger.BindsInstance;
import dagger.Subcomponent;
+import javax.inject.Named;
+
/**
* {@link InputSessionComponent} generates {@link InputSession} with specific instances bound for
* the session name and whether touches should be pilfered when consumed.
*/
-@Subcomponent
+@Subcomponent(
+ modules = { InputSessionModule.class }
+)
public interface InputSessionComponent {
/**
* Generates {@link InputSessionComponent}.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
new file mode 100644
index 000000000000..dfab666d5f59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch.dagger;
+
+import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+
+import android.view.GestureDetector;
+
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+import dagger.Module;
+import dagger.Provides;
+
+import javax.inject.Named;
+
+
+/**
+ * Module for providing dependencies to {@link com.android.systemui.dreams.touch.InputSession}.
+ */
+@Module
+public interface InputSessionModule {
+ /** */
+ @Provides
+ static InputMonitorCompat providesInputMonitorCompat(@Named(INPUT_SESSION_NAME) String name,
+ DisplayTracker displayTracker) {
+ return new InputMonitorCompat(name, displayTracker.getDefaultDisplayId());
+ }
+
+ /** */
+ @Provides
+ static GestureDetector providesGestureDetector(
+ android.view.GestureDetector.OnGestureListener gestureListener) {
+ return new GestureDetector(gestureListener);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index a6b01e748246..44f767aa321e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -19,7 +19,7 @@ package com.android.systemui.screenshot.policy
import android.content.ComponentName
import android.content.Context
import android.os.Process
-import com.android.systemui.Flags.screenshotPrivateProfile
+import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -72,7 +72,7 @@ interface ScreenshotPolicyModule {
displayContentRepoProvider: Provider<DisplayContentRepository>,
policyListProvider: Provider<List<CapturePolicy>>,
): ScreenshotRequestProcessor {
- return if (screenshotPrivateProfile()) {
+ return if (screenshotPrivateProfileBehaviorFix()) {
PolicyRequestProcessor(
background = background,
capture = imageCapture,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d9a51029d346..5f835b3697a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -95,7 +95,7 @@ object ScreenshotShelfViewBinder {
// mean that the new action must be inserted here.
val actionButton =
layoutInflater.inflate(
- R.layout.overlay_action_chip,
+ R.layout.shelf_action_chip,
actionsContainer,
false
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8b7b348ede76..79e6a0aa9c8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -67,6 +67,8 @@ import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -77,8 +79,6 @@ import java.util.Set;
import javax.inject.Inject;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* Controller which coordinates all the biometric unlocking actions with the UI.
*/
@@ -183,6 +183,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private final SystemClock mSystemClock;
private final boolean mOrderUnlockAndWake;
private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -323,6 +324,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mOrderUnlockAndWake = resources.getBoolean(
com.android.internal.R.bool.config_orderUnlockAndWake);
mSelectedUserInteractor = selectedUserInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
javaAdapter.alwaysCollectFlow(
keyguardTransitionInteractor.getStartedKeyguardTransitionStep(),
this::consumeTransitionStepOnStartedKeyguardState);
@@ -665,7 +667,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
if (isKeyguardShowing) {
if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
- || mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
+ || mKeyguardTransitionInteractor.getCurrentState()
+ == KeyguardState.ALTERNATE_BOUNCER) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed && bypass) {
return MODE_UNLOCK_COLLAPSING;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index 3b2930f78d19..f4e3eab8593d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,11 +42,10 @@ import kotlinx.coroutines.flow.asStateFlow
* using the default config for logging purposes.
*
* NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
- * [IntCarrierConfig.config]
- * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
- * when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
+ * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
+ * updated when a new carrier config comes down
*/
class SystemUiCarrierConfig
internal constructor(
@@ -68,16 +66,10 @@ internal constructor(
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
- private val satelliteHysteresisSeconds =
- IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
- /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
-
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
- satelliteHysteresisSeconds,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -98,19 +90,15 @@ internal constructor(
override fun toString(): String = trackedConfigs.joinToString { it.toString() }
}
-interface CarrierConfig {
- fun update(config: PersistableBundle)
-}
-
/** Extracts [key] from the carrier config, and stores it in a flow */
private class BooleanCarrierConfig(
val key: String,
defaultConfig: PersistableBundle,
-) : CarrierConfig {
+) {
private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
val config = _configValue.asStateFlow()
- override fun update(config: PersistableBundle) {
+ fun update(config: PersistableBundle) {
_configValue.value = config.getBoolean(key)
}
@@ -118,20 +106,3 @@ private class BooleanCarrierConfig(
return "$key=${config.value}"
}
}
-
-/** Extracts [key] from the carrier config, and stores it in a flow */
-private class IntCarrierConfig(
- val key: String,
- defaultConfig: PersistableBundle,
-) : CarrierConfig {
- private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
- val config = _configValue.asStateFlow()
-
- override fun update(config: PersistableBundle) {
- _configValue.value = config.getInt(key)
- }
-
- override fun toString(): String {
- return "$key=${config.value}"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 8f00b43e79f8..22785979f3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -44,6 +44,9 @@ interface MobileConnectionRepository {
/** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */
val carrierId: StateFlow<Int>
+ /** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
+ val inflateSignalStrength: StateFlow<Boolean>
+
/**
* The table log buffer created for this connection. Will have the name "MobileConnectionLog
* [subId]"
@@ -141,9 +144,6 @@ interface MobileConnectionRepository {
*/
val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
- /** Duration in seconds of the hysteresis to use when losing satellite connection. */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int>
-
/**
* True if this connection is in emergency callback mode.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index af34a57c7242..83d5f2b5d325 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.F
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
@@ -67,6 +68,17 @@ class DemoMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _carrierId.value)
+ private val _inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val inflateSignalStrength =
+ _inflateSignalStrength
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = "inflate",
+ _inflateSignalStrength.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
+
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly =
_isEmergencyOnly
@@ -191,7 +203,16 @@ class DemoMobileConnectionRepository(
.logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
.stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
- override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
+ override val numberOfLevels =
+ _inflateSignalStrength
+ .map { shouldInflate ->
+ if (shouldInflate) {
+ DEFAULT_NUM_LEVELS + 1
+ } else {
+ DEFAULT_NUM_LEVELS
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
override val dataEnabled = MutableStateFlow(true)
@@ -206,8 +227,6 @@ class DemoMobileConnectionRepository(
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
override suspend fun isInEcmMode(): Boolean = false
/**
@@ -226,8 +245,7 @@ class DemoMobileConnectionRepository(
_carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID
- numberOfLevels.value =
- if (event.inflateStrength) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
+ _inflateSignalStrength.value = event.inflateStrength
cdmaRoaming.value = event.roaming
_isRoaming.value = event.roaming
@@ -258,7 +276,6 @@ class DemoMobileConnectionRepository(
carrierName.value = NetworkNameModel.SubscriptionDerived(CARRIER_MERGED_NAME)
// TODO(b/276943904): is carrierId a thing with carrier merged networks?
_carrierId.value = INVALID_SUBSCRIPTION_ID
- numberOfLevels.value = event.numberOfLevels
cdmaRoaming.value = false
_primaryLevel.value = event.level
_cdmaLevel.value = event.level
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 2bc3bcbc8bf5..a532e6227451 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -165,6 +165,7 @@ class CarrierMergedConnectionRepository(
override val isRoaming = MutableStateFlow(false).asStateFlow()
override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow()
+ override val inflateSignalStrength = MutableStateFlow(false).asStateFlow()
override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
override val isInService = MutableStateFlow(true).asStateFlow()
@@ -186,9 +187,6 @@ class CarrierMergedConnectionRepository(
*/
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
- /** Non-applicable to carrier merged connections. */
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
-
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
override suspend fun isInEcmMode(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index b085d8046b12..41559b2c1455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
@@ -24,6 +25,7 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -291,6 +293,21 @@ class FullMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
+ override val inflateSignalStrength =
+ activeRepo
+ .flatMapLatest { it.inflateSignalStrength }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = "inflate",
+ initialValue = activeRepo.value.inflateSignalStrength.value,
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.inflateSignalStrength.value
+ )
+
override val numberOfLevels =
activeRepo
.flatMapLatest { it.numberOfLevels }
@@ -334,17 +351,29 @@ class FullMobileConnectionRepository(
activeRepo.value.hasPrioritizedNetworkCapabilities.value,
)
- override val satelliteConnectionHysteresisSeconds =
- activeRepo
- .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- activeRepo.value.satelliteConnectionHysteresisSeconds.value
- )
-
override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
+ fun dump(pw: PrintWriter) {
+ val ipw = IndentingPrintWriter(pw, " ")
+
+ ipw.println("MobileConnectionRepository[$subId]")
+ ipw.increaseIndent()
+
+ ipw.println("carrierMerged=${_isCarrierMerged.value}")
+
+ ipw.print("Type (cellular or carrier merged): ")
+ when (activeRepo.value) {
+ is CarrierMergedConnectionRepository -> ipw.println("Carrier merged")
+ is MobileConnectionRepositoryImpl -> ipw.println("Cellular")
+ }
+
+ ipw.increaseIndent()
+ ipw.println("Provider: ${activeRepo.value}")
+ ipw.decreaseIndent()
+
+ ipw.decreaseIndent()
+ }
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 5ab2ae899370..b3885d247949 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -302,8 +302,10 @@ class MobileConnectionRepositoryImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
+ override val inflateSignalStrength = systemUiCarrierConfig.shouldInflateSignalStrength
+
override val numberOfLevels =
- systemUiCarrierConfig.shouldInflateSignalStrength
+ inflateSignalStrength
.map { shouldInflate ->
if (shouldInflate) {
DEFAULT_NUM_LEVELS + 1
@@ -448,9 +450,6 @@ class MobileConnectionRepositoryImpl(
.flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
- systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
-
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a455db2e67ce..5d91ef323ead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -26,17 +26,20 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
@@ -52,6 +55,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -97,8 +102,12 @@ constructor(
wifiRepository: WifiRepository,
private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) : MobileConnectionsRepository {
- private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
+ private val dumpManager: DumpManager,
+) : MobileConnectionsRepository, Dumpable {
+
+ // TODO(b/333912012): for now, we are never invalidating the cache. We can do better though
+ private var subIdRepositoryCache:
+ MutableMap<Int, WeakReference<FullMobileConnectionRepository>> =
mutableMapOf()
private val defaultNetworkName =
@@ -109,6 +118,10 @@ constructor(
private val networkNameSeparator: String =
context.getString(R.string.status_bar_network_name_separator)
+ init {
+ dumpManager.registerNormalDumpable("MobileConnectionsRepository", this)
+ }
+
private val carrierMergedSubId: StateFlow<Int?> =
combine(
wifiRepository.wifiNetwork,
@@ -283,8 +296,10 @@ constructor(
getOrCreateRepoForSubId(subId)
private fun getOrCreateRepoForSubId(subId: Int) =
- subIdRepositoryCache[subId]
- ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ subIdRepositoryCache[subId]?.get()
+ ?: createRepositoryForSubId(subId).also {
+ subIdRepositoryCache[subId] = WeakReference(it)
+ }
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
@@ -374,9 +389,8 @@ constructor(
}
private fun updateRepos(newInfos: List<SubscriptionModel>) {
- dropUnusedReposFromCache(newInfos)
subIdRepositoryCache.forEach { (subId, repo) ->
- repo.setIsCarrierMerged(isCarrierMerged(subId))
+ repo.get()?.setIsCarrierMerged(isCarrierMerged(subId))
}
}
@@ -384,13 +398,6 @@ constructor(
return subId == carrierMergedSubId.value
}
- private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
- // Remove any connection repository from the cache that isn't in the new set of IDs. They
- // will get garbage collected once their subscribers go away
- subIdRepositoryCache =
- subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
- }
-
/**
* True if the checked subId is in the list of current subs or the active mobile data subId
*
@@ -422,6 +429,22 @@ constructor(
profileClass = profileClass,
)
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ val ipw = IndentingPrintWriter(pw, " ")
+ ipw.println("Connection cache:")
+
+ ipw.increaseIndent()
+ subIdRepositoryCache.entries.forEach { (subId, repo) ->
+ ipw.println("$subId: ${repo.get()}")
+ }
+ ipw.decreaseIndent()
+
+ ipw.println("Connections (${subIdRepositoryCache.size} total):")
+ ipw.increaseIndent()
+ subIdRepositoryCache.values.forEach { it.get()?.dump(ipw) }
+ ipw.decreaseIndent()
+ }
+
companion object {
private const val LOGGING_PREFIX = "Repo"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 9d194cfca350..ed9e4056535f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -35,25 +35,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.kotlin.pairwiseBy
-import kotlin.time.DurationUnit
-import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
interface MobileIconInteractor {
/** The table log created for this connection */
@@ -269,43 +262,6 @@ class MobileIconInteractorImpl(
MutableStateFlow(false).asStateFlow()
}
- private val hysteresisActive = MutableStateFlow(false)
-
- private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
- combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
- if (hysteresisActive) {
- true
- } else {
- isNonTerrestrial
- }
- }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnName = "isNonTerrestrialWithHysteresis",
- columnPrefix = "",
- initialValue = Flags.carrierEnabledSatelliteFlag(),
- )
- .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
-
- private val lostSatelliteConnection =
- isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
-
- init {
- scope.launch { lostSatelliteConnection.collect() }
- scope.launch {
- hysteresisActive.collectLatest {
- if (it) {
- delay(
- connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
- DurationUnit.SECONDS
- )
- )
- hysteresisActive.value = false
- }
- }
- }
- }
-
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -367,8 +323,11 @@ class MobileIconInteractorImpl(
combine(
level,
isInService,
- ) { level, isInService ->
- if (isInService) level else 0
+ connectionRepository.inflateSignalStrength,
+ ) { level, isInService, inflate ->
+ if (isInService) {
+ if (inflate) level + 1 else level
+ } else 0
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -404,7 +363,7 @@ class MobileIconInteractorImpl(
showExclamationMark.value,
carrierNetworkChangeActive.value,
)
- isNonTerrestrialWithHysteresis
+ isNonTerrestrial
.flatMapLatest { ntn ->
if (ntn) {
satelliteIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index eda5c44b5c2f..103b0e3a6f27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions
import com.android.systemui.Flags.statusBarStaticInoutIndicators
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -50,7 +50,7 @@ interface MobileIconViewModelCommon {
/** True if this view should be visible at all. */
val isVisible: StateFlow<Boolean>
val icon: Flow<SignalIconModel>
- val contentDescription: Flow<ContentDescription>
+ val contentDescription: Flow<ContentDescription?>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
val networkTypeIcon: Flow<Icon.Resource?>
@@ -123,7 +123,7 @@ class MobileIconViewModel(
override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
- override val contentDescription: Flow<ContentDescription> =
+ override val contentDescription: Flow<ContentDescription?> =
vmProvider.flatMapLatest { it.contentDescription }
override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
@@ -206,12 +206,26 @@ private class CellularIconViewModel(
override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
- override val contentDescription: Flow<ContentDescription> = run {
- val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[0])
+ override val contentDescription: Flow<ContentDescription?> =
iconInteractor.signalLevelIcon
- .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it.level]) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ .map {
+ // We expect the signal icon to be cellular here since this is the cellular vm
+ if (it !is SignalIconModel.Cellular) {
+ null
+ } else {
+ val resId =
+ AccessibilityContentDescriptions.getDescriptionForLevel(
+ it.level,
+ it.numberOfLevels
+ )
+ if (resId != 0) {
+ ContentDescription.Resource(resId)
+ } else {
+ null
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
private val showNetworkTypeIcon: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 7edb5a55a9ae..df19013ce2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -17,6 +17,7 @@
package com.android.systemui.volume;
import android.media.AudioManager;
+import android.util.MathUtils;
import android.view.View;
/**
@@ -46,4 +47,27 @@ class Util extends com.android.settingslib.volume.Util {
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.GONE);
}
+
+ /**
+ * Translates a value from one range to another.
+ *
+ * ```
+ * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+ * Result: 37.5
+ * ```
+ */
+ public static float translateToRange(float value,
+ float valueRangeStart,
+ float valueRangeEnd,
+ float targetRangeStart,
+ float targetRangeEnd) {
+ float currentRangeLength = valueRangeEnd - valueRangeStart;
+ float targetRangeLength = targetRangeEnd - targetRangeStart;
+ if (currentRangeLength == 0f || targetRangeLength == 0f) {
+ return targetRangeStart;
+ }
+ float valueFraction = (value - valueRangeStart) / currentRangeLength;
+ return MathUtils.constrain(targetRangeStart + valueFraction * targetRangeLength,
+ targetRangeStart, targetRangeEnd);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1688b0b51b41..22455417b647 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -137,13 +137,13 @@ import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import dagger.Lazy;
+
/**
* Visual presentation of the volume dialog.
*
@@ -166,6 +166,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
private static final int DRAWER_ANIMATION_DURATION = 250;
+ private static final int DISPLAY_RANGE_MULTIPLIER = 100;
/** Shows volume dialog show animation. */
private static final String TYPE_SHOW = "show";
@@ -826,12 +827,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
writer.print(" mSilentMode: "); writer.println(mSilentMode);
}
- private static int getImpliedLevel(SeekBar seekBar, int progress) {
- final int m = seekBar.getMax();
- final int n = m / 100 - 1;
- final int level = progress == 0 ? 0
- : progress == m ? (m / 100) : (1 + (int) ((progress / (float) m) * n));
- return level;
+ private static int getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress) {
+ return (int) Util.translateToRange(progress, seekBar.getMin(), seekBar.getMax(),
+ state.levelMin, state.levelMax);
+ }
+
+ private static int getProgressFromVolume(StreamState state, SeekBar seekBar, int volume) {
+ return (int) Util.translateToRange(volume, state.levelMin, state.levelMax, seekBar.getMin(),
+ seekBar.getMax());
}
@SuppressLint("InflateParams")
@@ -854,6 +857,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
addSliderHapticsToRow(row);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
+ row.slider.setAccessibilityDelegate(
+ new VolumeDialogSeekBarAccessibilityDelegate(DISPLAY_RANGE_MULTIPLIER));
row.anim = null;
@@ -1916,12 +1921,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
: false;
// update slider max
- final int max = ss.levelMax * 100;
+ final int max = ss.levelMax * DISPLAY_RANGE_MULTIPLIER;
if (max != row.slider.getMax()) {
row.slider.setMax(max);
}
// update slider min
- final int min = ss.levelMin * 100;
+ final int min = ss.levelMin * DISPLAY_RANGE_MULTIPLIER;
if (min != row.slider.getMin()) {
row.slider.setMin(min);
}
@@ -2069,7 +2074,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
return; // don't update if user is sliding
}
final int progress = row.slider.getProgress();
- final int level = getImpliedLevel(row.slider, progress);
+ final int level = getVolumeFromProgress(row.ss, row.slider, progress);
final boolean rowVisible = row.view.getVisibility() == VISIBLE;
final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
< USER_ATTEMPT_GRACE_PERIOD;
@@ -2085,7 +2090,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
return; // don't clamp if visible
}
}
- final int newProgress = vlevel * 100;
+ final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
if (progress != newProgress) {
if (mShowing && rowVisible) {
// animate!
@@ -2530,13 +2535,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
+ " onProgressChanged " + progress + " fromUser=" + fromUser);
if (!fromUser) return;
if (mRow.ss.levelMin > 0) {
- final int minProgress = mRow.ss.levelMin * 100;
+ final int minProgress = getProgressFromVolume(mRow.ss, seekBar, mRow.ss.levelMin);
if (progress < minProgress) {
seekBar.setProgress(minProgress);
progress = minProgress;
}
}
- final int userLevel = getImpliedLevel(seekBar, progress);
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, progress);
if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
mRow.userAttempt = SystemClock.uptimeMillis();
if (mRow.requestedLevel != userLevel) {
@@ -2569,7 +2574,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
}
mRow.tracking = false;
mRow.userAttempt = SystemClock.uptimeMillis();
- final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, seekBar.getProgress());
Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
if (mRow.ss.level != userLevel) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
new file mode 100644
index 000000000000..cd31a9531db6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.os.Bundle
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.SeekBar
+import com.android.internal.R
+
+class VolumeDialogSeekBarAccessibilityDelegate(
+ private val accessibilityStep: Int,
+) : AccessibilityDelegate() {
+
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+ require(host is SeekBar) { "This class only works with the SeekBar" }
+ val seekBar: SeekBar = host
+ if (
+ action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD ||
+ action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ ) {
+ var increment = accessibilityStep
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ increment = -increment
+ }
+
+ return super.performAccessibilityAction(
+ host,
+ R.id.accessibilityActionSetProgress,
+ Bundle().apply {
+ putFloat(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE,
+ (seekBar.progress + increment).coerceIn(seekBar.min, seekBar.max).toFloat(),
+ )
+ },
+ )
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
index 9f9275baf4f9..e5c5a655c73e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
-import com.android.systemui.common.shared.model.Color
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
data class SpatialAudioButtonViewModel(
val model: SpatialAudioEnabledModel,
val button: ToggleButtonViewModel,
- val iconColor: Color,
- val labelColor: Color,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4ecdd46163f9..b5e9ed27d664 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -18,7 +18,6 @@ package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
import android.content.Context
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
@@ -79,26 +78,7 @@ constructor(
val isChecked = isEnabled == currentIsEnabled
val buttonViewModel: ToggleButtonViewModel =
isEnabled.toViewModel(isChecked)
- SpatialAudioButtonViewModel(
- button = buttonViewModel,
- model = isEnabled,
- iconColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnPrimaryContainer
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- labelColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnSurface
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- )
+ SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
}
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index e0764205c85a..44207a0c434b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -477,9 +477,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
});
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -594,10 +593,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
- defaultMagnificationWindowSize / 2);
- // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+ // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
// created and we place the mirrored content as a child of the SurfaceView
- // (3) the animation starts (4) the animation updates (5) the animation ends
- verify(mTransaction, times(5))
+ // (3) the animation starts (4) the animation updates
+ verify(mTransaction, times(4))
.setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
}
@@ -788,9 +787,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
waitForIdleSync();
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -832,10 +830,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
- // the final duration time.
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index a88654bdbecc..01e4d58b68c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -46,6 +46,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -459,6 +460,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
targetCenterX, targetCenterY, mAnimationCallback);
@@ -491,6 +493,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT
final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
centerX + 10, centerY + 10, mAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
new file mode 100644
index 000000000000..8a1a08249856
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingInteractorTest : SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val bluetoothState = MutableStateFlow(false)
+ private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+ @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+ @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+ @Mock private lateinit var deviceItem: DeviceItem
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var audioSharingInteractor: AudioSharingInteractor
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+ whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+ audioSharingInteractor =
+ AudioSharingInteractor(
+ localBluetoothManager,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun testButtonStateUpdate_bluetoothOff_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_noDevice_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf())
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasSource_returnGone() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(false)
+ whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index a8f82eda51c7..6fe7d86faab8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -23,6 +23,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,7 +42,8 @@ import org.mockito.junit.MockitoRule
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class BluetoothStateInteractorTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
@@ -52,7 +54,12 @@ class BluetoothStateInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
bluetoothStateInteractor =
- BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher
+ )
`when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
}
@@ -61,7 +68,7 @@ class BluetoothStateInteractorTest : SysuiTestCase() {
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(true)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isTrue()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isTrue()
}
}
@@ -70,7 +77,7 @@ class BluetoothStateInteractorTest : SysuiTestCase() {
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isFalse()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isFalse()
}
}
@@ -79,7 +86,7 @@ class BluetoothStateInteractorTest : SysuiTestCase() {
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = true
+ bluetoothStateInteractor.setBluetoothEnabled(true)
verify(bluetoothAdapter).enable()
verify(logger)
.logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
@@ -91,7 +98,7 @@ class BluetoothStateInteractorTest : SysuiTestCase() {
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = false
+ bluetoothStateInteractor.setBluetoothEnabled(false)
verify(bluetoothAdapter, never()).enable()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 12dfe97649d3..62c98b05cbd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -110,7 +110,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -211,7 +210,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -267,7 +265,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
cachedHeight,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -291,7 +288,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -315,7 +311,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 6d99c5b62e9b..b05d9591d8a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -52,7 +52,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
@@ -74,7 +73,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor
+ @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -92,6 +91,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
+
@Mock
private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
BluetoothTileDialogDelegate.Factory
@@ -115,7 +116,12 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
bluetoothTileDialogViewModel =
BluetoothTileDialogViewModel(
deviceItemInteractor,
- bluetoothStateInteractor,
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ bluetoothTileDialogLogger,
+ testScope.backgroundScope,
+ dispatcher
+ ),
// TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
BluetoothAutoOnInteractor(
BluetoothAutoOnRepository(
@@ -125,6 +131,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
dispatcher
)
),
+ audioSharingInteractor,
mDialogTransitionAnimator,
activityStarter,
uiEventLogger,
@@ -135,20 +142,9 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
mBluetoothTileDialogDelegateDelegateFactory
)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
- whenever(bluetoothStateInteractor.bluetoothStateUpdate)
- .thenReturn(MutableStateFlow(null).asStateFlow())
whenever(deviceItemInteractor.deviceItemUpdateRequest)
.thenReturn(MutableStateFlow(Unit).asStateFlow())
- whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
- whenever(
- mBluetoothTileDialogDelegateDelegateFactory.create(
- any(),
- anyInt(),
- ArgumentMatchers.anyBoolean(),
- any(),
- any()
- )
- )
+ whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
.thenReturn(bluetoothTileDialogDelegate)
whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
whenever(sysuiDialog.context).thenReturn(mContext)
@@ -159,6 +155,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
+ whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
+ .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
}
@Test
@@ -201,15 +199,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
}
@Test
- fun testShowDialog_withBluetoothStateValue() {
- testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(null)
-
- verify(bluetoothStateInteractor).bluetoothStateUpdate
- }
- }
-
- @Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index eb735cbfec47..daf4a3cbb9de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -281,7 +281,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
) = isFilterMatchFunc(cachedDevice)
override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
new file mode 100644
index 000000000000..8685384bb243
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.EnableFlags;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Choreographer;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A test suite for exercising {@link InputSession}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
+public class InputSessionTest extends SysuiTestCase {
+ @Mock
+ InputMonitorCompat mInputMonitor;
+
+ @Mock
+ GestureDetector mGestureDetector;
+
+ @Mock
+ InputChannelCompat.InputEventListener mInputEventListener;
+
+ TestableLooper mLooper;
+
+ @Mock
+ Choreographer mChoreographer;
+
+ @Mock
+ InputChannelCompat.InputEventReceiver mInputEventReceiver;
+
+ InputSession mSession;
+
+ InputChannelCompat.InputEventListener mEventListener;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mLooper = TestableLooper.get(this);
+ }
+
+ private void createSession(boolean pilfer) {
+ when(mInputMonitor.getInputReceiver(any(), any(), any()))
+ .thenReturn(mInputEventReceiver);
+ mSession = new InputSession(mInputMonitor, mGestureDetector,
+ mInputEventListener, mChoreographer, mLooper.getLooper(), pilfer);
+ final ArgumentCaptor<InputChannelCompat.InputEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mInputMonitor).getInputReceiver(any(), any(), listenerCaptor.capture());
+ mEventListener = listenerCaptor.getValue();
+ }
+
+ /**
+ * Ensures consumed motion events are pilfered when option is set.
+ */
+ @Test
+ public void testPilferOnMotionEventGestureConsume() {
+ createSession(true);
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
+ mEventListener.onInputEvent(event);
+ verify(mInputEventListener).onInputEvent(eq(event));
+ verify(mInputMonitor).pilferPointers();
+ }
+
+ /**
+ * Ensures consumed motion events are not pilfered when option is not set.
+ */
+ @Test
+ public void testNoPilferOnMotionEventGestureConsume() {
+ createSession(false);
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
+ mEventListener.onInputEvent(event);
+ verify(mInputEventListener).onInputEvent(eq(event));
+ verify(mInputMonitor, never()).pilferPointers();
+ }
+
+ /**
+ * Ensures input events are never pilfered.
+ */
+ @Test
+ public void testNoPilferOnInputEvent() {
+ createSession(true);
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ mEventListener.onInputEvent(event);
+ verify(mInputEventListener).onInputEvent(eq(event));
+ verify(mInputMonitor, never()).pilferPointers();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE)
+ public void testPilferOnce() {
+ createSession(true);
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
+ mEventListener.onInputEvent(event);
+ mEventListener.onInputEvent(event);
+ verify(mInputEventListener, times(2)).onInputEvent(eq(event));
+ verify(mInputMonitor, times(1)).pilferPointers();
+ }
+
+ /**
+ * Ensures components are properly disposed.
+ */
+ @Test
+ public void testDispose() {
+ createSession(true);
+ mSession.dispose();
+ verify(mInputMonitor).dispose();
+ verify(mInputEventReceiver).dispose();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 50f81ff13825..e9ec3236a06c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -131,6 +131,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private BiometricUnlockInteractor mBiometricUnlockInteractor;
+ @Mock
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final FakeSystemClock mSystemClock = new FakeSystemClock();
private BiometricUnlockController mBiometricUnlockController;
@@ -167,7 +169,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mBiometricUnlockInteractor,
mock(JavaAdapter.class),
- mock(KeyguardTransitionInteractor.class)
+ mKeyguardTransitionInteractor
);
biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -374,6 +376,24 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void onBiometricAuthenticated_whenFaceOnAlternateBouncer_dismissBouncer() {
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(false);
+ when(mKeyguardTransitionInteractor.getCurrentState())
+ .thenReturn(KeyguardState.ALTERNATE_BOUNCER);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
+ mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
+
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+ assertThat(mBiometricUnlockController.getBiometricType())
+ .isEqualTo(BiometricSourceType.FACE);
+ }
+
+ @Test
public void onBiometricAuthenticated_whenBypassOnBouncer_dismissBouncer() {
reset(mKeyguardBypassController);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 9b6940e14415..598b12ccdc38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -137,6 +137,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
wifiRepository,
mock(),
mock(),
+ mock(),
)
demoRepo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index c13e830afac7..3c13906dbd43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.net.ConnectivityManager
+import android.os.PersistableBundle
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -99,6 +100,9 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
)
)
+ // Use a real config, with no overrides
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_ID, PersistableBundle())
+
private lateinit var mobileRepo: FakeMobileConnectionRepository
private lateinit var carrierMergedRepo: FakeMobileConnectionRepository
@@ -680,10 +684,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
telephonyManager: TelephonyManager,
): MobileConnectionRepositoryImpl {
whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
- val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock()
- whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds)
- .thenReturn(MutableStateFlow(0))
-
val realRepo =
MobileConnectionRepositoryImpl(
SUB_ID,
@@ -693,7 +693,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
SEP,
connectivityManager,
telephonyManager,
- systemUiCarrierConfig = systemUiCarrierConfigMock,
+ systemUiCarrierConfig = systemUiCarrierConfig,
fakeBroadcastDispatcher,
mobileMappingsProxy = mock(),
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index f761bcfe63d6..9d1411625a8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -1030,6 +1030,26 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
+ fun inflateSignalStrength_usesCarrierConfig() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.inflateSignalStrength)
+
+ assertThat(latest).isEqualTo(false)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(true)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(false)
+ }
+
+ @Test
fun isAllowedDuringAirplaneMode_alwaysFalse() =
testScope.runTest {
val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 07abd275d1ce..b7a3b300a460 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -80,6 +80,7 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
@@ -229,6 +230,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
wifiRepository,
fullConnectionFactory,
updateMonitor,
+ mock(),
)
testScope.runCurrent()
@@ -529,6 +531,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -553,6 +556,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -581,6 +585,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
/** Regression test for b/261706421 */
@Test
+ @Ignore("b/333912012")
fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -604,6 +609,54 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
+ fun testConnectionsCache_keepsReposCached() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Sub1 comes back
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1_1).isSameInstanceAs(repo1_2)
+ }
+
+ @Test
+ fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Client grabs a reference to a repository, but doesn't keep it around
+ underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isNotNull()
+ }
+
+ @Test
fun testConnectionRepository_invalidSubId_doesNotThrow() =
testScope.runTest {
underTest.getRepoForSubId(SUB_1_ID)
@@ -1063,7 +1116,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
- updateMonitor
+ updateMonitor,
+ mock(),
)
val latest by collectLastValue(underTest.defaultDataSubRatConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c49fcf88ecaa..dfe80233918a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,15 +43,12 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.DurationUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -181,6 +178,22 @@ class MobileIconInteractorTest : SysuiTestCase() {
}
@Test
+ fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() =
+ testScope.runTest {
+ connectionRepository.inflateSignalStrength.value = false
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
+ assertThat(latest!!.level).isEqualTo(5)
+ }
+
+ @Test
fun iconGroup_three_g() =
testScope.runTest {
connectionRepository.resolvedNetworkType.value =
@@ -678,32 +691,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
- @Test
- fun satBasedIcon_hasHysteresisWhenDisabled() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
-
- val hysteresisDuration = 5.seconds
- connectionRepository.satelliteConnectionHysteresisSeconds.value =
- hysteresisDuration.toInt(DurationUnit.SECONDS)
-
- connectionRepository.isNonTerrestrial.value = true
-
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Disable satellite
- connectionRepository.isNonTerrestrial.value = false
-
- // Satellite icon should still be visible
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Wait for the icon to change
- advanceTimeBy(hysteresisDuration)
-
- assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
- }
-
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 83d0fe8f9c4b..cec41557f344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupReposi
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
@@ -279,6 +280,76 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@Test
+ fun contentDescription_nonInflated_invalidLevelIsNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = false
+ repository.setAllLevels(-1)
+ assertThat(latest).isNull()
+
+ repository.setAllLevels(100)
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun contentDescription_inflated_invalidLevelIsNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = true
+ repository.numberOfLevels.value = 6
+ repository.setAllLevels(-2)
+ assertThat(latest).isNull()
+
+ repository.setAllLevels(100)
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun contentDescription_nonInflated_testABunchOfLevelsForNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+
+ repository.inflateSignalStrength.value = false
+ repository.numberOfLevels.value = 5
+
+ // -1 and 5 are out of the bounds for non-inflated content descriptions
+ for (i in -1..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -1,
+ 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull()
+ else ->
+ assertWithMessage("Level $i is expected not to be null")
+ .that(latest)
+ .isNotNull()
+ }
+ }
+ }
+
+ @Test
+ fun contentDescription_inflated_testABunchOfLevelsForNull() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.contentDescription)
+ repository.inflateSignalStrength.value = true
+ repository.numberOfLevels.value = 6
+ // -1 and 6 are out of the bounds for inflated content descriptions
+ // Note that the interactor adds 1 to the reported level, hence the -2 to 5 range
+ for (i in -2..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -2,
+ 5 -> assertWithMessage("Level $i is expected to be null").that(latest).isNull()
+ else ->
+ assertWithMessage("Level $i is not expected to be null")
+ .that(latest)
+ .isNotNull()
+ }
+ }
+ }
+
+ @Test
fun networkType_dataEnabled_groupIsRepresented() =
testScope.runTest {
val expected =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
index fb82b8fed76c..483dc0c9c974 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
@@ -15,14 +15,14 @@
*/
package com.android.systemui.volume;
+import static com.google.common.truth.Truth.assertThat;
+
import android.media.MediaMetadata;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import junit.framework.Assert;
-
import org.junit.Test;
@SmallTest
@@ -30,11 +30,59 @@ public class UtilTest extends SysuiTestCase {
@Test
public void testMediaMetadataToString_null() {
- Assert.assertEquals(null, Util.mediaMetadataToString(null));
+ assertThat(Util.mediaMetadataToString(null)).isNull();
}
@Test
public void testMediaMetadataToString_notNull() {
- Assert.assertNotNull(Util.mediaMetadataToString(new MediaMetadata.Builder().build()));
+ assertThat(Util.mediaMetadataToString(new MediaMetadata.Builder().build())).isNotNull();
+ }
+
+ @Test
+ public void translateToRange_translatesStartToStart() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 0,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(0);
+ }
+
+ @Test
+ public void translateToRange_translatesValueToValue() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 4,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(400);
+ }
+
+ @Test
+ public void translateToRange_translatesEndToEnd() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
+ }
+
+ @Test
+ public void translateToRange_returnsStartForEmptyRange() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 7,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 700,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index ed2fb2c0cfc9..3b468aa011ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -248,6 +248,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
ss.name = STREAMS.get(i);
ss.level = 1;
+ ss.levelMin = 0;
+ ss.levelMax = 25;
state.states.append(i, ss);
}
return state;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a6dd3cd7d30a..219794f3ad18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -32,6 +32,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.currentTime
@@ -68,6 +70,8 @@ class FakeAuthenticationRepository(
var lockoutStartedReportCount = 0
+ private val credentialCheckingMutex = Mutex(locked = false)
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return authenticationMethod.value
}
@@ -124,30 +128,32 @@ class FakeAuthenticationRepository(
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
- val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
- val isSuccessful =
- when {
- credential.type != getCurrentCredentialType(securityMode) -> false
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
- credential.isPin && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
- credential.isPassword && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
- credential.isPattern && credential.matches(expectedCredential)
- else -> error("Unexpected credential type ${credential.type}!")
+ return credentialCheckingMutex.withLock {
+ val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+ val isSuccessful =
+ when {
+ credential.type != getCurrentCredentialType(securityMode) -> false
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+ credential.isPin && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+ credential.isPassword && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+ credential.isPattern && credential.matches(expectedCredential)
+ else -> error("Unexpected credential type ${credential.type}!")
+ }
+
+ val failedAttempts = _failedAuthenticationAttempts.value
+ if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ AuthenticationResultModel(
+ isSuccessful = isSuccessful,
+ lockoutDurationMs = 0,
+ )
+ } else {
+ AuthenticationResultModel(
+ isSuccessful = false,
+ lockoutDurationMs = LOCKOUT_DURATION_MS,
+ )
}
-
- val failedAttempts = _failedAuthenticationAttempts.value
- return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- AuthenticationResultModel(
- isSuccessful = isSuccessful,
- lockoutDurationMs = 0,
- )
- } else {
- AuthenticationResultModel(
- isSuccessful = false,
- lockoutDurationMs = LOCKOUT_DURATION_MS,
- )
}
}
@@ -155,6 +161,23 @@ class FakeAuthenticationRepository(
_isPinEnhancedPrivacyEnabled.value = isEnabled
}
+ /**
+ * Pauses any future credential checking. The test must call [unpauseCredentialChecking] to
+ * flush the accumulated credential checks.
+ */
+ suspend fun pauseCredentialChecking() {
+ credentialCheckingMutex.lock()
+ }
+
+ /**
+ * Unpauses future credential checking, if it was paused using [pauseCredentialChecking]. This
+ * doesn't flush any pending coroutine jobs; the test code may still choose to do that using
+ * `runCurrent`.
+ */
+ fun unpauseCredentialChecking() {
+ credentialCheckingMutex.unlock()
+ }
+
private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
return when (val credentialType = getCurrentCredentialType(securityMode)) {
LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 2d5a3612ff6a..eb2d6c0f5405 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -31,6 +31,7 @@ class FakeMobileConnectionRepository(
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID)
+ override val inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isEmergencyOnly = MutableStateFlow(false)
override val isRoaming = MutableStateFlow(false)
override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
@@ -63,8 +64,6 @@ class FakeMobileConnectionRepository(
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
private var isInEcmMode: Boolean = false
override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto
index d97bf816b150..fc9f7a4590bd 100644
--- a/proto/src/am_capabilities.proto
+++ b/proto/src/am_capabilities.proto
@@ -7,6 +7,16 @@ message Capability {
string name = 1;
}
+message VMCapability {
+ string name = 1;
+}
+
+message FrameworkCapability {
+ string name = 1;
+}
+
message Capabilities {
repeated Capability values = 1;
+ repeated VMCapability vm_capabilities = 2;
+ repeated FrameworkCapability framework_capabilities = 3;
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index bfa1c7bb99d0..8ab2e0fa6379 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -56,6 +56,13 @@ flag {
}
flag {
+ name: "enable_hardware_shortcut_disables_warning"
+ namespace: "accessibility"
+ description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+ bug: "287065325"
+}
+
+flag {
name: "enable_magnification_joystick"
namespace: "accessibility"
description: "Whether to enable joystick controls for magnification"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e64e500c9b65..ccf9a90b5964 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4273,6 +4273,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
if (shortcutType == UserShortcutType.HARDWARE) {
skipVolumeShortcutDialogTimeoutRestriction(userId);
+ if (com.android.server.accessibility.Flags.enableHardwareShortcutDisablesWarning()) {
+ persistIntToSetting(
+ userId,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.SHOWN
+ );
+ }
} else if (shortcutType == UserShortcutType.SOFTWARE) {
// Update the A11y FAB size to large when the Magnification shortcut is
// enabled and the user hasn't changed the floating button size
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 23373f1df63c..afeafa4b6373 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -302,7 +302,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
if (Flags.interceptIntentsBeforeApplyingPolicy()) {
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
@@ -318,7 +318,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
}
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
}
@@ -331,15 +331,17 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
boolean isNewTask) {
// Mirror displays cannot contain activities.
if (waitAndGetIsMirrorDisplay()) {
- Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
+ logActivityLaunchBlocked("Mirror virtual displays cannot contain activities.");
return false;
}
if (!isWindowingModeSupported(windowingMode)) {
- Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
+ logActivityLaunchBlocked(
+ "Virtual device doesn't support windowing mode " + windowingMode);
return false;
}
if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
- Slog.d(TAG, "Virtual device requires android:canDisplayOnRemoteDevices=true");
+ logActivityLaunchBlocked(
+ "Activity requires android:canDisplayOnRemoteDevices=true");
return false;
}
final UserHandle activityUser =
@@ -350,11 +352,11 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
return true;
}
if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) {
- Slog.d(TAG, "Virtual device launch disallowed from user " + activityUser);
+ logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser);
return false;
}
if (!activityMatchesDisplayCategory(activityInfo)) {
- Slog.d(TAG, "The activity's required display category '"
+ logActivityLaunchBlocked("The activity's required display category '"
+ activityInfo.requiredDisplayCategory
+ "' not found on virtual display with the following categories: "
+ mDisplayCategories);
@@ -363,7 +365,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
synchronized (mGenericWindowPolicyControllerLock) {
if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
activityComponent)) {
- Slog.d(TAG, "Virtual device launch disallowed by policy: "
+ logActivityLaunchBlocked("Activity launch disallowed by policy: "
+ activityComponent);
return false;
}
@@ -371,7 +373,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
&& !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
mCrossTaskNavigationExemptions, activityComponent)) {
- Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+ logActivityLaunchBlocked("Cross task navigation disallowed by policy: "
+ activityComponent);
return false;
}
@@ -380,12 +382,18 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
// based on FLAG_STREAM_PERMISSIONS
if (mPermissionDialogComponent != null
&& mPermissionDialogComponent.equals(activityComponent)) {
+ logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
return false;
}
return true;
}
+ private void logActivityLaunchBlocked(String reason) {
+ Slog.d(TAG, "Virtual device activity launch disallowed on display "
+ + waitAndGetDisplayId() + ", reason: " + reason);
+ }
+
@Override
@SuppressWarnings("AndroidFrameworkRequiresPermission")
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 1741593ba671..ccc44a41759b 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
+
import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
@@ -70,12 +72,6 @@ public class GestureLauncherService extends SystemService {
*/
@VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
- /**
- * Min time in milliseconds to complete the emergency gesture for it count. If the gesture is
- * completed faster than this, we assume it's not performed by human and the
- * event gets ignored.
- */
- @VisibleForTesting static final int EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS = 200;
/**
* Interval in milliseconds in which the power button must be depressed in succession to be
@@ -570,7 +566,8 @@ public class GestureLauncherService extends SystemService {
long emergencyGestureTapDetectionMinTimeMs = Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS,
- EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS);
+ mContext.getResources().getInteger(
+ config_defaultMinEmergencyGestureTapDurationMillis));
if (emergencyGestureSpentTime <= emergencyGestureTapDetectionMinTimeMs) {
Slog.i(TAG, "Emergency gesture detected but it's too fast. Gesture time: "
+ emergencyGestureSpentTime + " ms");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8022eb37fce7..5ec6b7200191 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4437,8 +4437,16 @@ public class ActivityManagerService extends IActivityManager.Stub
final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
&& packageStateStopped);
if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
+ final int cancelReason;
+ if (packageName == null) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+ } else if (uninstalling) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_UNINSTALLED;
+ } else {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+ }
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
- packageName, userId, appId, doit);
+ packageName, userId, appId, doit, cancelReason);
}
if (doit) {
@@ -10167,7 +10175,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)));
}
@@ -10182,13 +10194,30 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
- true);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "removeApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), true);
}
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int userId) {
enforceNotIsolatedCaller("addStartInfoTimestamp");
+
+ // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+ throw new IllegalArgumentException("Unsupported userId");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addStartInfoTimestamp", null);
+
+ final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e70722ca6579..372ec45763bf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -97,6 +97,7 @@ import android.opengl.GLES10;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IProgressListener;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
@@ -125,6 +126,8 @@ import com.android.server.LocalServices;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.nano.Capabilities;
import com.android.server.am.nano.Capability;
+import com.android.server.am.nano.FrameworkCapability;
+import com.android.server.am.nano.VMCapability;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
@@ -443,6 +446,22 @@ final class ActivityManagerShellCommand extends ShellCommand {
capabilities.values[i] = cap;
}
+ String[] vmCapabilities = Debug.getVmFeatureList();
+ capabilities.vmCapabilities = new VMCapability[vmCapabilities.length];
+ for (int i = 0; i < vmCapabilities.length; i++) {
+ VMCapability cap = new VMCapability();
+ cap.name = vmCapabilities[i];
+ capabilities.vmCapabilities[i] = cap;
+ }
+
+ String[] fmCapabilities = Debug.getFeatureList();
+ capabilities.frameworkCapabilities = new FrameworkCapability[fmCapabilities.length];
+ for (int i = 0; i < fmCapabilities.length; i++) {
+ FrameworkCapability cap = new FrameworkCapability();
+ cap.name = fmCapabilities[i];
+ capabilities.frameworkCapabilities[i] = cap;
+ }
+
try {
getRawOutputStream().write(Capabilities.toByteArray(capabilities));
} catch (IOException e) {
@@ -452,10 +471,16 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else {
// Unfortunately we don't have protobuf text format capabilities here.
// Fallback to line separated list instead for text parser.
- pw.println("Format: 1");
+ pw.println("Format: 2");
for (String capability : CAPABILITIES) {
pw.println(capability);
}
+ for (String capability : Debug.getVmFeatureList()) {
+ pw.println("vm:" + capability);
+ }
+ for (String capability : Debug.getFeatureList()) {
+ pw.println("framework:" + capability);
+ }
}
return 0;
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 8b1300b641a9..ef015ee9d743 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2153,9 +2153,12 @@ public final class AppRestrictionController {
mRestrictionSettings.update(pkgName, uid, level, reason, subReason);
}
- if (!allowUpdateBucket || curBucket == STANDBY_BUCKET_EXEMPTED) {
+ if (!android.app.Flags.appRestrictionsApi()
+ && (!allowUpdateBucket || curBucket == STANDBY_BUCKET_EXEMPTED)) {
return;
}
+
+ boolean doItNow = true;
if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
&& curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
// Moving the app standby bucket to restricted in the meanwhile.
@@ -2168,7 +2171,6 @@ public final class AppRestrictionController {
&& (mConstantsObserver.mBgAutoRestrictedBucket
|| level == RESTRICTION_LEVEL_RESTRICTED_BUCKET)) {
// restrict the app if it hasn't done so.
- boolean doIt = true;
synchronized (mSettingsLock) {
final int index = mActiveUids.indexOfKey(uid, pkgName);
if (index >= 0) {
@@ -2182,14 +2184,16 @@ public final class AppRestrictionController {
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level,
localTrackerInfo, localReason);
});
- doIt = false;
+ doItNow = false;
}
}
- if (doIt) {
+ if (doItNow) {
appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid),
reason, subReason);
- logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
- reason);
+ if (!android.app.Flags.appRestrictionsApi()) {
+ logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
+ reason);
+ }
}
}
} else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
@@ -2204,6 +2208,13 @@ public final class AppRestrictionController {
appStandbyInternal.maybeUnrestrictApp(pkgName, UserHandle.getUserId(uid),
prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK,
reason, subReason);
+ if (!android.app.Flags.appRestrictionsApi()) {
+ logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
+ reason);
+ }
+ }
+
+ if (doItNow && android.app.Flags.appRestrictionsApi()) {
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
reason);
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index ddf1d5f5ab71..0728ea8e5604 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -464,7 +464,7 @@ public final class AppStartInfoTracker {
addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
}
- private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
+ void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
synchronized (mLock) {
AppStartInfoContainer container = mData.get(packageName, uid);
if (container == null) {
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index fb0d6957129a..f3361203c44a 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -22,6 +22,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
import android.annotation.Nullable;
import android.app.Activity;
@@ -54,6 +56,7 @@ import com.android.internal.util.RingBuffer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
+import com.android.server.am.PendingIntentRecord.CancellationReason;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.SafeActivityOptions;
@@ -191,7 +194,7 @@ public class PendingIntentController {
}
return rec;
}
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED);
mIntentSenderRecords.remove(key);
decrementUidStatLocked(rec);
}
@@ -206,7 +209,7 @@ public class PendingIntentController {
}
boolean removePendingIntentsForPackage(String packageName, int userId, int appId,
- boolean doIt) {
+ boolean doIt, @CancellationReason int cancelReason) {
boolean didSomething = false;
synchronized (mLock) {
@@ -256,7 +259,7 @@ public class PendingIntentController {
}
didSomething = true;
it.remove();
- makeIntentSenderCanceled(pir);
+ makeIntentSenderCanceled(pir, cancelReason);
decrementUidStatLocked(pir);
if (pir.key.activity != null) {
final Message m = PooledLambda.obtainMessage(
@@ -289,13 +292,14 @@ public class PendingIntentController {
} catch (RemoteException e) {
throw new SecurityException(e);
}
- cancelIntentSender(rec, true);
+ cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED);
}
}
- public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
+ public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity,
+ @CancellationReason int cancelReason) {
synchronized (mLock) {
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, cancelReason);
mIntentSenderRecords.remove(rec.key);
decrementUidStatLocked(rec);
if (cleanActivity && rec.key.activity != null) {
@@ -359,8 +363,10 @@ public class PendingIntentController {
}
}
- private void makeIntentSenderCanceled(PendingIntentRecord rec) {
+ private void makeIntentSenderCanceled(PendingIntentRecord rec,
+ @CancellationReason int cancelReason) {
rec.canceled = true;
+ rec.cancelReason = cancelReason;
final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
if (callbacks != null) {
final Message m = PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130ed1194..da45a7727faf 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,11 +16,13 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -51,11 +53,15 @@ import android.util.ArraySet;
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.expresslog.Counter;
import com.android.server.wm.SafeActivityOptions;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -71,12 +77,35 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
public static final int FLAG_BROADCAST_SENDER = 1 << 1;
public static final int FLAG_SERVICE_SENDER = 1 << 2;
+ public static final int CANCEL_REASON_NULL = 0;
+ public static final int CANCEL_REASON_USER_STOPPED = 1 << 0;
+ public static final int CANCEL_REASON_OWNER_UNINSTALLED = 1 << 1;
+ public static final int CANCEL_REASON_OWNER_FORCE_STOPPED = 1 << 2;
+ public static final int CANCEL_REASON_OWNER_CANCELED = 1 << 3;
+ public static final int CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED = 1 << 4;
+ public static final int CANCEL_REASON_SUPERSEDED = 1 << 5;
+ public static final int CANCEL_REASON_ONE_SHOT_SENT = 1 << 6;
+
+ @IntDef({
+ CANCEL_REASON_NULL,
+ CANCEL_REASON_USER_STOPPED,
+ CANCEL_REASON_OWNER_UNINSTALLED,
+ CANCEL_REASON_OWNER_FORCE_STOPPED,
+ CANCEL_REASON_OWNER_CANCELED,
+ CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED,
+ CANCEL_REASON_SUPERSEDED,
+ CANCEL_REASON_ONE_SHOT_SENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CancellationReason {}
+
final PendingIntentController controller;
final Key key;
final int uid;
public final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
+ @CancellationReason int cancelReason = CANCEL_REASON_NULL;
/**
* Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
* milliseconds, Integer is allowlist type defined at
@@ -419,12 +448,22 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
SafeActivityOptions mergedOptions = null;
synchronized (controller.mLock) {
if (canceled) {
+ if (cancelReason == CANCEL_REASON_OWNER_FORCE_STOPPED
+ && controller.mAmInternal.getUidProcessState(callingUid)
+ == PROCESS_STATE_TOP) {
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_caller",
+ callingUid);
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_owner",
+ uid);
+ }
return ActivityManager.START_CANCELED;
}
sent = true;
if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
- controller.cancelIntentSender(this, true);
+ controller.cancelIntentSender(this, true, CANCEL_REASON_ONE_SHOT_SENT);
}
finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();
@@ -687,6 +726,21 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
}
}
+ @VisibleForTesting
+ static String cancelReasonToString(@CancellationReason int cancelReason) {
+ return switch (cancelReason) {
+ case CANCEL_REASON_NULL -> "NULL";
+ case CANCEL_REASON_USER_STOPPED -> "USER_STOPPED";
+ case CANCEL_REASON_OWNER_UNINSTALLED -> "OWNER_UNINSTALLED";
+ case CANCEL_REASON_OWNER_FORCE_STOPPED -> "OWNER_FORCE_STOPPED";
+ case CANCEL_REASON_OWNER_CANCELED -> "OWNER_CANCELED";
+ case CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED -> "HOSTING_ACTIVITY_DESTROYED";
+ case CANCEL_REASON_SUPERSEDED -> "SUPERSEDED";
+ case CANCEL_REASON_ONE_SHOT_SENT -> "ONE_SHOT_SENT";
+ default -> "UNKNOWN";
+ };
+ }
+
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
@@ -707,7 +761,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
}
if (sent || canceled) {
pw.print(prefix); pw.print("sent="); pw.print(sent);
- pw.print(" canceled="); pw.println(canceled);
+ pw.print(" canceled="); pw.print(canceled);
+ pw.print(" cancelReason="); pw.println(cancelReasonToString(cancelReason));
}
if (mAllowlistDuration != null) {
pw.print(prefix);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index be39778372ca..83fa34490f32 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -248,6 +248,7 @@ public class AppOpsService extends IAppOpsService.Stub {
Process.ROOT_UID,
Process.PHONE_UID,
Process.BLUETOOTH_UID,
+ Process.AUDIOSERVER_UID,
Process.NFC_UID,
Process.NETWORK_STACK_UID,
Process.SHELL_UID};
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 6f3526fb07e9..dbd47d00718f 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -54,13 +54,13 @@ import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.AtomicDirectory;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.FgThread;
+import com.android.server.IoThread;
import org.xmlpull.v1.XmlPullParserException;
@@ -669,7 +669,7 @@ final class HistoricalRegistry {
}
private void clearHistoryOnDiskDLocked() {
- BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
synchronized (mInMemoryLock) {
mCurrentHistoricalOps = null;
mNextPersistDueTimeMillis = System.currentTimeMillis();
@@ -745,7 +745,7 @@ final class HistoricalRegistry {
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
synchronized (mOnDiskLock) {
- BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
+ IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
if (pendingWrites.isEmpty()) {
return;
}
@@ -767,7 +767,7 @@ final class HistoricalRegistry {
final Message message = PooledLambda.obtainMessage(
HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
message.what = MSG_WRITE_PENDING_HISTORY;
- BackgroundThread.getHandler().sendMessage(message);
+ IoThread.getHandler().sendMessage(message);
mPendingWrites.offerFirst(ops);
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da2fc51..dd6433d98553 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@ package com.android.server.inputmethod;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@ final class AdditionalSubtypeMapRepository {
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@ final class AdditionalSubtypeMapRepository {
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 03a85c40ef31..e41b47f8f00e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -869,9 +869,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ if (userId == mSettings.getUserId()) {
+ mSettings = settings;
+ }
+ }
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
@@ -1118,12 +1126,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
-
- if (!isCurrentUser) {
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
return;
}
mSettings = queryInputMethodServicesInternal(mContext, userId,
@@ -1281,21 +1292,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
- }
- if (userId != currentUserId) {
- return;
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+ + mSettings.getUserId());
}
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mSettings.getUserId() == userId) {
+ mSettings = newSettings;
+ // We need to rebuild IMEs.
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ }
}
}
@@ -1361,12 +1373,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
final int userId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ mSettings = InputMethodSettingsRepository.get(userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1529,8 +1542,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- mSettings = queryInputMethodServicesInternal(mContext, newUserId,
- AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(newUserId, newSettings);
+ mSettings = newSettings;
postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
@@ -1612,9 +1627,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final String defaultImiId = mSettings.getSelectedInputMethod();
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- mSettings = queryInputMethodServicesInternal(mContext, currentUserId,
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ mSettings = newSettings;
postInputMethodSettingUpdatedLocked(
!imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
@@ -4076,22 +4093,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- mSettings = queryInputMethodServicesInternal(mContext,
- mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
+ mSettings = newSettings;
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 000000000000..60b9a4cfe840
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 194ab04817ec..3d6855547bcd 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1458,14 +1458,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
@Override
public IBinder getBinderForSetQueue() throws RemoteException {
return new ParcelableListBinder<QueueItem>(
+ QueueItem.class,
(list) -> {
- // Checking list items are instanceof QueueItem to validate against
- // malicious apps calling it directly via reflection with non compilable
- // items. See b/317048338 for more details
- List<QueueItem> sanitizedQueue =
- list.stream().filter(it -> it instanceof QueueItem).toList();
synchronized (mLock) {
- mQueue = sanitizedQueue;
+ mQueue = list;
}
mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
});
diff --git a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
index 2cc63ebfc962..a3a91e26c5cc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
@@ -25,4 +25,6 @@ import android.annotation.Nullable;
interface NotificationManagerPrivate {
@Nullable
NotificationRecord getNotificationByKey(String key);
+
+ void timeoutNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b14242ef8e08..8075ae0e4d61 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -139,7 +139,6 @@ import static android.service.notification.NotificationListenerService.TRIM_FULL
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
-
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -238,6 +237,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.DeviceIdleManager;
import android.os.Environment;
import android.os.Handler;
@@ -305,7 +305,6 @@ import android.view.Display;
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.Toast;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -357,9 +356,7 @@ import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
-
import libcore.io.IoUtils;
-
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
@@ -521,7 +518,7 @@ public class NotificationManagerService extends SystemService {
/**
* Apps that post custom toasts in the background will have those blocked. Apps can
* still post toasts created with
- * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
+ * {@link Toast#makeText(Context, CharSequence, int)} and its variants while
* in the background.
*/
@ChangeId
@@ -556,7 +553,7 @@ public class NotificationManagerService extends SystemService {
/**
* Rate limit showing toasts, on a per package basis.
*
- * It limits the number of {@link android.widget.Toast#show()} calls to prevent overburdening
+ * It limits the number of {@link Toast#show()} calls to prevent overburdening
* the user with too many toasts in a limited time. Any attempt to show more toasts than allowed
* in a certain time frame will result in the toast being discarded.
*/
@@ -580,9 +577,9 @@ public class NotificationManagerService extends SystemService {
static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
/**
- * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and
- * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the
- * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
+ * App calls to {@link NotificationManager#setInterruptionFilter} and
+ * {@link NotificationManager#setNotificationPolicy} manage DND through the
+ * creation and activation of an implicit {@link AutomaticZenRule}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -624,6 +621,8 @@ public class NotificationManagerService extends SystemService {
private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
+ private LockPatternUtils mLockUtils;
+
final IBinder mForegroundToken = new Binder();
@VisibleForTesting
WorkerHandler mHandler;
@@ -709,6 +708,7 @@ public class NotificationManagerService extends SystemService {
private NotificationHistoryManager mHistoryManager;
protected SnoozeHelper mSnoozeHelper;
+ private TimeToLiveHelper mTtlHelper;
private GroupHelper mGroupHelper;
private int mAutoGroupAtCount;
private boolean mIsTelevision;
@@ -734,6 +734,8 @@ public class NotificationManagerService extends SystemService {
// Broadcast intent receiver for notification permissions review-related intents
private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+ private AppOpsManager.OnOpChangedListener mAppOpsListener;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -779,7 +781,7 @@ public class NotificationManagerService extends SystemService {
public StatusBarNotification[] getArray(UserManager um, int count, boolean includeSnoozed) {
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : um.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -902,14 +904,14 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting
boolean isDNDMigrationDone(int userId) {
- return Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+ return Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
}
@VisibleForTesting
void setDNDMigrationDone(int userId) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 1, userId);
}
protected void migrateDefaultNAS() {
@@ -936,15 +938,15 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting
void setNASMigrationDone(int baseUserId) {
for (int profileId : mUm.getProfileIds(baseUserId, false)) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 1, profileId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 1, profileId);
}
}
@VisibleForTesting
boolean isNASMigrationDone(int userId) {
- return (Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
+ return (Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
}
boolean isProfileUser(UserInfo userInfo) {
@@ -1097,7 +1099,7 @@ public class NotificationManagerService extends SystemService {
mSnoozeHelper.readXml(parser, System.currentTimeMillis());
}
if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mLockScreenAllowSecureNotifications = parser.getAttributeBoolean(null,
@@ -1141,7 +1143,7 @@ public class NotificationManagerService extends SystemService {
InputStream infile = null;
try {
infile = mPolicyFile.openRead();
- readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+ readPolicyXml(infile, false /*forRestore*/, USER_ALL);
// We re-load the default dnd packages to allow the newly added and denined.
final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1191,7 +1193,7 @@ public class NotificationManagerService extends SystemService {
}
try {
- writePolicyXml(stream, false /*forBackup*/, UserHandle.USER_ALL);
+ writePolicyXml(stream, false /*forBackup*/, USER_ALL);
mPolicyFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1220,7 +1222,7 @@ public class NotificationManagerService extends SystemService {
mAssistants.writeXml(out, forBackup, userId);
mSnoozeHelper.writeXml(out);
mConditionProviders.writeXml(out, forBackup, userId);
- if (!forBackup || userId == UserHandle.USER_SYSTEM) {
+ if (!forBackup || userId == USER_SYSTEM) {
writeSecureNotificationsPolicy(out);
}
out.endTag(null, TAG_NOTIFICATION_POLICY);
@@ -1273,7 +1275,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
- sbn.getId(), Notification.FLAG_AUTO_CANCEL,
+ sbn.getId(), FLAG_AUTO_CANCEL,
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
@@ -1761,9 +1763,50 @@ public class NotificationManagerService extends SystemService {
};
- NotificationManagerPrivate mNotificationManagerPrivate = key -> {
- synchronized (mNotificationLock) {
- return mNotificationsByKey.get(key);
+ NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
+ @Nullable
+ @Override
+ public NotificationRecord getNotificationByKey(String key) {
+ synchronized (mNotificationLock) {
+ return mNotificationsByKey.get(key);
+ }
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ public void timeoutNotification(String key) {
+ boolean foundNotification = false;
+ int uid = 0;
+ int pid = 0;
+ String packageName = null;
+ String tag = null;
+ int id = 0;
+ int userId = 0;
+
+ synchronized (mNotificationLock) {
+ NotificationRecord record = findNotificationByKeyLocked(key);
+ if (record != null) {
+ foundNotification = true;
+ uid = record.getUid();
+ pid = record.getSbn().getInitialPid();
+ packageName = record.getSbn().getPackageName();
+ tag = record.getSbn().getTag();
+ id = record.getSbn().getId();
+ userId = record.getUserId();
+ }
+ }
+ if (foundNotification) {
+ if (lifetimeExtensionRefactor()) {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+ | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
+ true, userId, REASON_TIMEOUT, null);
+ } else {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+ true, userId, REASON_TIMEOUT, null);
+ }
+ }
}
};
@@ -1893,7 +1936,7 @@ public class NotificationManagerService extends SystemService {
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
|| action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_ALL);
+ USER_ALL);
String pkgList[] = null;
int uidList[] = null;
boolean removingPackage = queryRemove &&
@@ -1942,7 +1985,7 @@ public class NotificationManagerService extends SystemService {
try {
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
- changeUserId != UserHandle.USER_ALL ? changeUserId :
+ changeUserId != USER_ALL ? changeUserId :
USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -2055,22 +2098,22 @@ public class NotificationManagerService extends SystemService {
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
+ = Secure.getUriFor(Secure.NOTIFICATION_BADGING);
private final Uri NOTIFICATION_BUBBLES_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+ = Secure.getUriFor(Secure.NOTIFICATION_BUBBLES);
private final Uri NOTIFICATION_RATE_LIMIT_URI
= Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
private final Uri NOTIFICATION_HISTORY_ENABLED
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+ = Secure.getUriFor(Secure.NOTIFICATION_HISTORY_ENABLED);
private final Uri NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI
= Settings.Global.getUriFor(Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS);
private final Uri LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
- = Settings.Secure.getUriFor(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ = Secure.getUriFor(
+ Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
- = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ = Secure.getUriFor(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
private final Uri SHOW_NOTIFICATION_SNOOZE
- = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
+ = Secure.getUriFor(Secure.SHOW_NOTIFICATION_SNOOZE);
SettingsObserver(Handler handler) {
super(handler);
@@ -2079,27 +2122,31 @@ public class NotificationManagerService extends SystemService {
void observe() {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
update(null);
}
+ void destroy() {
+ getContext().getContentResolver().unregisterContentObserver(this);
+ }
+
@Override public void onChange(boolean selfChange, Uri uri, int userId) {
update(uri);
}
@@ -2131,7 +2178,7 @@ public class NotificationManagerService extends SystemService {
mPreferencesHelper.updateLockScreenShowNotifications();
}
if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
- final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+ final boolean snoozeEnabled = Secure.getIntForUser(resolver,
Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
!= 0;
if (!snoozeEnabled) {
@@ -2144,8 +2191,8 @@ public class NotificationManagerService extends SystemService {
ContentResolver resolver = getContext().getContentResolver();
if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
mArchive.updateHistoryEnabled(userId,
- Settings.Secure.getIntForUser(resolver,
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+ Secure.getIntForUser(resolver,
+ Secure.NOTIFICATION_HISTORY_ENABLED, 0,
userId) == 1);
// note: this setting is also handled in NotificationHistoryManager
}
@@ -2229,6 +2276,11 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
+ void setLockPatternUtils(LockPatternUtils lockUtils) {
+ mLockUtils = lockUtils;
+ }
+
+ @VisibleForTesting
ShortcutHelper getShortcutHelper() {
return mShortcutHelper;
}
@@ -2400,7 +2452,7 @@ public class NotificationManagerService extends SystemService {
getContext().sendBroadcastAsUser(
new Intent(ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
- UserHandle.ALL, permission.MANAGE_NOTIFICATIONS);
+ UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
@@ -2465,6 +2517,9 @@ public class NotificationManagerService extends SystemService {
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper = new TimeToLiveHelper(mNotificationManagerPrivate, getContext());
+ }
// This is a ManagedServices object that keeps track of the listeners.
mListeners = notificationListeners;
@@ -2551,10 +2606,12 @@ public class NotificationManagerService extends SystemService {
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
null);
- IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
- timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
- getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (!Flags.allNotifsNeedTtl()) {
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
@@ -2567,15 +2624,16 @@ public class NotificationManagerService extends SystemService {
ReviewNotificationPermissionsReceiver.getFilter(),
Context.RECEIVER_NOT_EXPORTED);
- mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
- new AppOpsManager.OnOpChangedInternalListener() {
- @Override
- public void onOpChanged(@NonNull String op, @NonNull String packageName,
- int userId) {
- mHandler.post(
- () -> handleNotificationPermissionChange(packageName, userId));
- }
- });
+ mAppOpsListener = new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(@NonNull String op, @NonNull String packageName,
+ int userId) {
+ mHandler.post(
+ () -> handleNotificationPermissionChange(packageName, userId));
+ }
+ };
+
+ mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener);
}
/**
@@ -2584,10 +2642,26 @@ public class NotificationManagerService extends SystemService {
public void onDestroy() {
getContext().unregisterReceiver(mIntentReceiver);
getContext().unregisterReceiver(mPackageIntentReceiver);
- getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.destroy();
+ } else {
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ }
getContext().unregisterReceiver(mRestoreReceiver);
getContext().unregisterReceiver(mLocaleChangeReceiver);
+ mSettingsObserver.destroy();
+ mRoleObserver.destroy();
+ if (mShortcutHelper != null) {
+ mShortcutHelper.destroy();
+ }
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+ mAppOps.stopWatchingMode(mAppOpsListener);
+ mAlarmManager.cancelAll();
+
if (mDeviceConfigChangedListener != null) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
}
@@ -2842,7 +2916,10 @@ public class NotificationManagerService extends SystemService {
bubbsExtractor.setShortcutHelper(mShortcutHelper);
}
registerNotificationPreferencesPullers();
- new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+ if (mLockUtils == null) {
+ mLockUtils = new LockPatternUtils(getContext());
+ }
+ mLockUtils.registerStrongAuthTracker(mStrongAuthTracker);
mAttentionHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -2952,7 +3029,7 @@ public class NotificationManagerService extends SystemService {
void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
boolean fromListener) {
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ if (channel.getImportance() == IMPORTANCE_NONE) {
// cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
@@ -3217,14 +3294,14 @@ public class NotificationManagerService extends SystemService {
| SUPPRESSED_EFFECT_SCREEN_OFF);
// set the deprecated effects according to the new more specific effects
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) != 0) {
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_ON;
}
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) != 0
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
+ & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_AMBIENT) != 0) {
+ & SUPPRESSED_EFFECT_AMBIENT) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_OFF;
}
} else {
@@ -3292,7 +3369,7 @@ public class NotificationManagerService extends SystemService {
if (n.extras != null) {
title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
if (title == null) {
- title = n.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+ title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
}
}
return title == null ? getContext().getResources().getString(
@@ -3311,9 +3388,9 @@ public class NotificationManagerService extends SystemService {
if (nb.getStyle() instanceof Notification.BigTextStyle) {
text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
- } else if (nb.getStyle() instanceof Notification.MessagingStyle) {
- Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
- final List<Notification.MessagingStyle.Message> messages = ms.getMessages();
+ } else if (nb.getStyle() instanceof MessagingStyle) {
+ MessagingStyle ms = (MessagingStyle) nb.getStyle();
+ final List<MessagingStyle.Message> messages = ms.getMessages();
if (messages != null && messages.size() > 0) {
text = messages.get(messages.size() - 1).getText();
}
@@ -3364,7 +3441,7 @@ public class NotificationManagerService extends SystemService {
}
private int getRealUserId(int userId) {
- return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
+ return userId == USER_ALL ? USER_SYSTEM : userId;
}
private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
@@ -4096,8 +4173,8 @@ public class NotificationManagerService extends SystemService {
public void createConversationNotificationChannelForPackage(String pkg, int uid,
NotificationChannel parentChannel, String conversationId) {
enforceSystemOrSystemUI("only system can call this");
- Preconditions.checkNotNull(parentChannel);
- Preconditions.checkNotNull(conversationId);
+ checkNotNull(parentChannel);
+ checkNotNull(conversationId);
String parentId = parentChannel.getId();
NotificationChannel conversationChannel = parentChannel;
conversationChannel.setId(String.format(
@@ -4542,7 +4619,7 @@ public class NotificationManagerService extends SystemService {
int uid = Binder.getCallingUid();
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : mUm.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -4798,7 +4875,7 @@ public class NotificationManagerService extends SystemService {
* Register a listener binder directly with the notification manager.
*
* Only works with system callers. Apps should extend
- * {@link android.service.notification.NotificationListenerService}.
+ * {@link NotificationListenerService}.
*/
@Override
public void registerListener(final INotificationListener listener,
@@ -4855,7 +4932,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL &&
+ if (userId != info.userid && userId != USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -4972,7 +5049,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL
+ if (userId != info.userid && userId != USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -5639,7 +5716,7 @@ public class NotificationManagerService extends SystemService {
}
private void enforcePolicyAccess(int uid, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5670,7 +5747,7 @@ public class NotificationManagerService extends SystemService {
}
private void enforcePolicyAccess(String pkg, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5691,7 +5768,7 @@ public class NotificationManagerService extends SystemService {
try {
uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
UserHandle.getCallingUserId());
- if (PackageManager.PERMISSION_GRANTED == checkComponentPermission(
+ if (PERMISSION_GRANTED == checkComponentPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
-1, true)) {
return true;
@@ -5886,7 +5963,7 @@ public class NotificationManagerService extends SystemService {
/**
* Sets the notification policy. Apps that target API levels below
- * {@link android.os.Build.VERSION_CODES#P} cannot change user-designated values to
+ * {@link Build.VERSION_CODES#P} cannot change user-designated values to
* allow or disallow {@link Policy#PRIORITY_CATEGORY_ALARMS},
* {@link Policy#PRIORITY_CATEGORY_SYSTEM} and
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
@@ -6254,7 +6331,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void setPrivateNotificationsAllowed(boolean allow) {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6275,7 +6352,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean getPrivateNotificationsAllowed() {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6563,9 +6640,9 @@ public class NotificationManagerService extends SystemService {
// Add summary
final ApplicationInfo appInfo =
adjustedSbn.getNotification().extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+ EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
final Bundle extras = new Bundle();
- extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+ extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
final Notification summaryNotification =
@@ -6859,6 +6936,11 @@ public class NotificationManagerService extends SystemService {
if (!zenOnly) {
pw.println("\n Usage Stats:");
mUsageStats.dump(pw, " ", filter);
+
+ if (Flags.allNotifsNeedTtl()) {
+ pw.println("\n TimeToLive alarms:");
+ mTtlHelper.dump(pw, " ");
+ }
}
}
}
@@ -7455,7 +7537,7 @@ public class NotificationManagerService extends SystemService {
throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
+ (userId == USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
@@ -7570,7 +7652,7 @@ public class NotificationManagerService extends SystemService {
// Enforce NO_CLEAR flag on MediaStyle notification for apps with targetSdk >= V.
if (CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION,
notificationUid)) {
- notification.flags |= Notification.FLAG_NO_CLEAR;
+ notification.flags |= FLAG_NO_CLEAR;
}
}
@@ -7622,7 +7704,7 @@ public class NotificationManagerService extends SystemService {
// Check if an app has been given system exemption
return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
- ai.packageName) == AppOpsManager.MODE_ALLOWED;
+ ai.packageName) == MODE_ALLOWED;
}
private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
@@ -7727,7 +7809,7 @@ public class NotificationManagerService extends SystemService {
// Enqueue will trigger resort & flag is updated that way.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
mHandler.post(
- new NotificationManagerService.EnqueueNotificationRunnable(
+ new EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -7749,7 +7831,7 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting
int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
// posted from app A on behalf of app A
@@ -8008,7 +8090,7 @@ public class NotificationManagerService extends SystemService {
final String pkg = r.getSbn().getPackageName();
final int callingUid = r.getSbn().getUid();
return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
- || r.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ || r.getImportance() == IMPORTANCE_NONE;
}
protected class SnoozeNotificationRunnable implements Runnable {
@@ -8347,7 +8429,11 @@ public class NotificationManagerService extends SystemService {
}
mEnqueuedNotifications.add(r);
- scheduleTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.scheduleTimeoutLocked(r, SystemClock.elapsedRealtime());
+ } else {
+ scheduleTimeoutLocked(r);
+ }
final StatusBarNotification n = r.getSbn();
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
@@ -8567,7 +8653,7 @@ public class NotificationManagerService extends SystemService {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
- NotificationListenerService.REASON_ERROR, r.getStats());
+ REASON_ERROR, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -8935,7 +9021,7 @@ public class NotificationManagerService extends SystemService {
try {
isExemptFromRateLimiting = mPackageManager.checkPermission(
android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect with package manager");
}
@@ -9460,7 +9546,11 @@ public class NotificationManagerService extends SystemService {
int rank, int count, boolean wasPosted, String listenerName,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final String canceledKey = r.getKey();
- cancelScheduledTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.cancelScheduledTimeoutLocked(r);
+ } else {
+ cancelScheduledTimeoutLocked(r);
+ }
// Record caller.
recordCallerLocked(r);
@@ -9637,7 +9727,7 @@ public class NotificationManagerService extends SystemService {
// Uri, not when removing an individual listener.
revokeUriPermission(permissionOwner, uri,
UserHandle.getUserId(oldRecord.getUid()),
- null, UserHandle.USER_ALL);
+ null, USER_ALL);
}
}
}
@@ -9735,9 +9825,9 @@ public class NotificationManagerService extends SystemService {
} else {
return
// looking for USER_ALL notifications? match everything
- userId == UserHandle.USER_ALL
+ userId == USER_ALL
// a notification sent to USER_ALL matches any query
- || r.getUserId() == UserHandle.USER_ALL
+ || r.getUserId() == USER_ALL
// an exact user match
|| r.getUserId() == userId;
}
@@ -9816,7 +9906,7 @@ public class NotificationManagerService extends SystemService {
continue;
}
// Don't remove notifications to all, if there's no package name specified
- if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+ if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == USER_ALL) {
continue;
}
if (!flagChecker.apply(r.getFlags())) {
@@ -10314,7 +10404,7 @@ public class NotificationManagerService extends SystemService {
return false;
}
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
@@ -10537,7 +10627,7 @@ public class NotificationManagerService extends SystemService {
if (requiredPermission != null) {
try {
if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
- != PackageManager.PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED) {
canUseManagedServices = false;
}
} catch (RemoteException e) {
@@ -10663,7 +10753,7 @@ public class NotificationManagerService extends SystemService {
c.caption = "notification assistant";
c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_ASSISTANT;
c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
c.clientLabel = R.string.notification_ranker_binding_label;
@@ -11259,7 +11349,7 @@ public class NotificationManagerService extends SystemService {
c.caption = "notification listener";
c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
c.clientLabel = R.string.notification_listener_binding_label;
@@ -11667,7 +11757,7 @@ public class NotificationManagerService extends SystemService {
// Managed Services.
if (info.isSystemUi() && old != null && old.getNotification() != null
&& (old.getNotification().flags
- & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
break;
@@ -11696,8 +11786,8 @@ public class NotificationManagerService extends SystemService {
continue;
}
// Grant access before listener is notified
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
mPackageManagerInternal.grantImplicitAccess(
@@ -11846,8 +11936,8 @@ public class NotificationManagerService extends SystemService {
continue;
}
// Grant or revoke access synchronously
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
if (grant) {
// Grant permissions by passing arguments as if the notification is new.
updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
@@ -11919,7 +12009,7 @@ public class NotificationManagerService extends SystemService {
}
// Revoke access after all listeners have been updated
- mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
+ mHandler.post(() -> updateUriPermissions(null, r, null, USER_SYSTEM));
}
/**
@@ -12079,7 +12169,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
@@ -12102,7 +12192,7 @@ public class NotificationManagerService extends SystemService {
reason = REASON_LISTENER_CANCEL;
}
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
@@ -12114,7 +12204,7 @@ public class NotificationManagerService extends SystemService {
final INotificationListener listener = (INotificationListener) info.service;
try {
listener.onNotificationRankingUpdate(rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
@@ -12333,6 +12423,10 @@ public class NotificationManagerService extends SystemService {
mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
}
+ void destroy() {
+ mRm.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.ALL);
+ }
+
@VisibleForTesting
public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
@@ -12621,7 +12715,7 @@ public class NotificationManagerService extends SystemService {
.setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.setStyle(new Notification.BigTextStyle())
- .setFlag(Notification.FLAG_NO_CLEAR, true)
+ .setFlag(FLAG_NO_CLEAR, true)
.setAutoCancel(true)
.addAction(remindMe)
.addAction(dismiss)
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 97d26208571c..c69bead309f1 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -79,6 +79,7 @@ import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
import java.lang.reflect.Array;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -600,8 +601,7 @@ public final class NotificationRecord {
pw.println(prefix + "headsUpContentView="
+ formatRemoteViews(notification.headsUpContentView));
pw.println(prefix + String.format("color=0x%08x", notification.color));
- pw.println(prefix + "timeout="
- + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
+ pw.println(prefix + "timeout=" + Duration.ofMillis(notification.getTimeoutAfter()));
if (notification.actions != null && notification.actions.length > 0) {
pw.println(prefix + "actions={");
final int N = notification.actions.length;
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index fc106b8ec4ac..86dcecf9290a 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -287,4 +287,11 @@ public class ShortcutHelper {
}
}
}
+
+ void destroy() {
+ if (mLauncherAppsCallbackRegistered) {
+ mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+ mLauncherAppsCallbackRegistered = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
new file mode 100644
index 000000000000..2facab74e28e
--- /dev/null
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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.notification;
+
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.PrintWriter;
+import java.util.TreeSet;
+
+/**
+ * Handles canceling notifications when their time to live expires
+ */
+@FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+public class TimeToLiveHelper {
+ private static final String TAG = TimeToLiveHelper.class.getSimpleName();
+ private static final String ACTION = "com.android.server.notification.TimeToLiveHelper";
+
+ private static final int REQUEST_CODE_TIMEOUT = 1;
+ private static final String SCHEME_TIMEOUT = "timeout";
+ static final String EXTRA_KEY = "key";
+ private final Context mContext;
+ private final NotificationManagerPrivate mNm;
+ private final AlarmManager mAm;
+
+ @VisibleForTesting
+ final TreeSet<Pair<Long, String>> mKeys;
+
+ public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) {
+ mContext = context;
+ mNm = nm;
+ mAm = context.getSystemService(AlarmManager.class);
+ mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+
+ IntentFilter timeoutFilter = new IntentFilter(ACTION);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ mContext.registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ void destroy() {
+ mContext.unregisterReceiver(mNotificationTimeoutReceiver);
+ }
+
+ void dump(PrintWriter pw, String indent) {
+ pw.println(indent + "mKeys " + mKeys);
+ }
+
+ private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ return PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+ .setData(new Uri.Builder()
+ .scheme(SCHEME_TIMEOUT)
+ .appendPath(nextKey)
+ .build())
+ .putExtra(EXTRA_KEY, nextKey)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ flags);
+ }
+
+ @VisibleForTesting
+ void scheduleTimeoutLocked(NotificationRecord record, long currentTime) {
+ removeMatchingEntry(record.getKey());
+
+ final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
+ if (record.getNotification().getTimeoutAfter() > 0) {
+ final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
+
+ // Maybe replace alarm with an earlier one
+ if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
+ if (currentEarliestTime != null) {
+ cancelFirstAlarm();
+ }
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ maybeScheduleFirstAlarm();
+ } else {
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void cancelScheduledTimeoutLocked(NotificationRecord record) {
+ removeMatchingEntry(record.getKey());
+ }
+
+ private void removeMatchingEntry(String key) {
+ if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) {
+ // cancel the first alarm, remove the first entry, maybe schedule the alarm for the new
+ // first entry
+ cancelFirstAlarm();
+ mKeys.remove(mKeys.first());
+ maybeScheduleFirstAlarm();
+ } else {
+ // just remove the entry
+ Pair<Long, String> trackedPair = null;
+ for (Pair<Long, String> entry : mKeys) {
+ if (key.equals(entry.second)) {
+ trackedPair = entry;
+ break;
+ }
+ }
+ if (trackedPair != null) {
+ mKeys.remove(trackedPair);
+ }
+ }
+ }
+
+ private void cancelFirstAlarm() {
+ final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT);
+ mAm.cancel(pi);
+ }
+
+ private void maybeScheduleFirstAlarm() {
+ if (!mKeys.isEmpty()) {
+ final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second,
+ FLAG_UPDATE_CURRENT);
+ mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mKeys.first().first, piNewFirst);
+ }
+ }
+
+ @VisibleForTesting
+ final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ if (ACTION.equals(action)) {
+ Pair<Long, String> earliest = mKeys.first();
+ String key = intent.getStringExtra(EXTRA_KEY);
+ if (!earliest.second.equals(key)) {
+ Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+ }
+ removeMatchingEntry(key);
+ mNm.timeoutNotification(earliest.second);
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index f7f76aaaee16..57ea233c0a2b 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -807,7 +807,7 @@ final class DefaultPermissionGrantPolicy {
getDefaultSystemHandlerActivityPackage(pm,
SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- NOTIFICATION_PERMISSIONS, PHONE_PERMISSIONS);
+ NOTIFICATION_PERMISSIONS, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS);
}
// Voice recognition
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2b43326f4c51..e280bdc7780b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -466,20 +466,17 @@ final class AccessibilityController {
}
}
- void drawMagnifiedRegionBorderIfNeeded(int displayId) {
- if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
- return;
- }
-
+ void recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(int displayId) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(
- TAG + ".drawMagnifiedRegionBorderIfNeeded",
+ TAG + ".recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId);
}
+
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
+ displayMagnifier.recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded();
}
// Not relevant for the window observer.
}
@@ -936,11 +933,13 @@ final class AccessibilityController {
}
}
- void drawMagnifiedRegionBorderIfNeeded() {
+ void recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
+ mAccessibilityTracing.logTrace(LOG_TAG
+ + ".recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK);
}
+ recomputeBounds();
if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.drawWindowIfNeeded();
@@ -1245,7 +1244,6 @@ final class AccessibilityController {
}
void drawWindowIfNeeded() {
- recomputeBounds();
mWindow.postDrawIfNeeded();
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83745edd8132..42373aa85053 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4260,7 +4260,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
PendingIntentRecord rec = apr.get();
if (rec != null) {
mAtmService.mPendingIntentController.cancelIntentSender(rec,
- false /* cleanActivity */);
+ false /* cleanActivity */,
+ PendingIntentRecord.CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED);
}
}
pendingResults = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a5687e6fa4ab..a0f615b1ea58 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3779,17 +3779,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
r.shortComponentName, Boolean.toString(isAutoEnter));
- r.setPictureInPictureParams(params);
- r.mAutoEnteringPip = isAutoEnter;
- mRootWindowContainer.moveActivityToPinnedRootTask(r,
- null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
- transition);
- // Continue the pausing process after entering pip.
- if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
- r.getTask().schedulePauseActivity(r, false /* userLeaving */,
- false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
- }
- r.mAutoEnteringPip = false;
+
+ // Ensure the ClientTransactionItems are bundled for this operation.
+ deferWindowLayout();
+ try {
+ r.setPictureInPictureParams(params);
+ r.mAutoEnteringPip = isAutoEnter;
+ mRootWindowContainer.moveActivityToPinnedRootTask(r,
+ null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+ transition);
+ // Continue the pausing process after entering pip.
+ if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
+ r.getTask().schedulePauseActivity(r, false /* userLeaving */,
+ false /* pauseImmediately */, true /* autoEnteringPip */,
+ "auto-pip");
+ }
+ r.mAutoEnteringPip = false;
+ } finally {
+ continueWindowLayout();
+ }
}
};
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b9979adbafd0..d709fa5726f1 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1762,18 +1762,39 @@ class BackNavigationController {
}
private void onBackNavigationDone(Bundle result, int backType) {
- boolean triggerBack = result != null && result.getBoolean(
- BackNavigationInfo.KEY_TRIGGER_BACK);
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
- + "triggerBack=%b", backType, triggerBack);
-
- synchronized (mWindowManagerService.mGlobalLock) {
- mNavigationMonitor.stopMonitorForRemote();
- mBackAnimationInProgress = false;
- mShowWallpaper = false;
- // All animation should be done, clear any un-send animation.
- mPendingAnimation = null;
- mPendingAnimationBuilder = null;
+ if (result == null) {
+ return;
+ }
+ if (result.containsKey(BackNavigationInfo.KEY_NAVIGATION_FINISHED)) {
+ final boolean triggerBack = result.getBoolean(
+ BackNavigationInfo.KEY_NAVIGATION_FINISHED);
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ + "triggerBack=%b", backType, triggerBack);
+
+ synchronized (mWindowManagerService.mGlobalLock) {
+ mNavigationMonitor.stopMonitorForRemote();
+ mBackAnimationInProgress = false;
+ mShowWallpaper = false;
+ // All animation should be done, clear any un-send animation.
+ mPendingAnimation = null;
+ mPendingAnimationBuilder = null;
+ }
+ }
+ if (result.getBoolean(BackNavigationInfo.KEY_GESTURE_FINISHED)) {
+ synchronized (mWindowManagerService.mGlobalLock) {
+ final AnimationHandler ah = mAnimationHandler;
+ if (!ah.mComposed || ah.mWaitTransition || ah.mOpenActivities == null
+ || (ah.mSwitchType != AnimationHandler.TASK_SWITCH
+ && ah.mSwitchType != AnimationHandler.ACTIVITY_SWITCH)) {
+ return;
+ }
+ for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
+ final ActivityRecord preDrawActivity = mAnimationHandler.mOpenActivities[i];
+ if (!preDrawActivity.mLaunchTaskBehind) {
+ setLaunchBehind(preDrawActivity);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 45cf10bd3f5e..5aa0ed7ce76c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -327,14 +327,14 @@ final class LetterboxConfiguration {
R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
- mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxHorizontalPositionMultiplier);
- mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxVerticalPositionMultiplier);
- mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxBookModePositionMultiplier);
- mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxTabletopModePositionMultiplier);
+ setLetterboxHorizontalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxHorizontalPositionMultiplier));
+ setLetterboxVerticalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxVerticalPositionMultiplier));
+ setLetterboxBookModePositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxBookModePositionMultiplier));
+ setLetterboxTabletopModePositionMultiplier(mContext.getResources()
+ .getFloat(R.dimen.config_letterboxTabletopModePositionMultiplier));
mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsHorizontalReachabilityEnabled);
mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
@@ -657,29 +657,8 @@ final class LetterboxConfiguration {
* right side.
*/
float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
- if (isInBookMode) {
- if (mLetterboxBookModePositionMultiplier < 0.0f
- || mLetterboxBookModePositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
- + mLetterboxBookModePositionMultiplier);
- // Default to left position if invalid value is provided.
- return 0.0f;
- } else {
- return mLetterboxBookModePositionMultiplier;
- }
- } else {
- if (mLetterboxHorizontalPositionMultiplier < 0.0f
- || mLetterboxHorizontalPositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
- + mLetterboxBookModePositionMultiplier);
- // Default to central position if invalid value is provided.
- return 0.5f;
- } else {
- return mLetterboxHorizontalPositionMultiplier;
- }
- }
+ return isInBookMode ? mLetterboxBookModePositionMultiplier
+ : mLetterboxHorizontalPositionMultiplier;
}
/*
@@ -689,37 +668,28 @@ final class LetterboxConfiguration {
* bottom side.
*/
float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
- if (isInTabletopMode) {
- return (mLetterboxTabletopModePositionMultiplier < 0.0f
- || mLetterboxTabletopModePositionMultiplier > 1.0f)
- // Default to top position if invalid value is provided.
- ? 0.0f : mLetterboxTabletopModePositionMultiplier;
- } else {
- return (mLetterboxVerticalPositionMultiplier < 0.0f
- || mLetterboxVerticalPositionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : mLetterboxVerticalPositionMultiplier;
- }
+ return isInTabletopMode ? mLetterboxTabletopModePositionMultiplier
+ : mLetterboxVerticalPositionMultiplier;
}
/**
- * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides horizontal position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxHorizontalPositionMultiplier(float multiplier) {
- mLetterboxHorizontalPositionMultiplier = multiplier;
+ mLetterboxHorizontalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxHorizontalPositionMultiplier");
}
/**
- * Overrides vertical position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides vertical position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxVerticalPositionMultiplier(float multiplier) {
- mLetterboxVerticalPositionMultiplier = multiplier;
+ mLetterboxVerticalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxVerticalPositionMultiplier");
}
/**
@@ -740,6 +710,28 @@ final class LetterboxConfiguration {
com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier);
}
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxTabletopModePositionMultiplier(float multiplier) {
+ mLetterboxTabletopModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxTabletopModePositionMultiplier");
+ }
+
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxBookModePositionMultiplier(float multiplier) {
+ mLetterboxBookModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxBookModePositionMultiplier");
+ }
+
/*
* Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
* landscape device orientation.
@@ -1356,4 +1348,21 @@ final class LetterboxConfiguration {
void resetUserAppAspectRatioFullscreenEnabled() {
setUserAppAspectRatioFullscreenOverrideEnabled(false);
}
+
+ /**
+ * Checks whether the multiplier is between [0,1].
+ *
+ * @param multiplierName sent in the exception if multiplier is invalid, for easier debugging.
+ *
+ * @return multiplier, if valid
+ * @throws IllegalArgumentException if outside bounds.
+ */
+ private float assertValidMultiplier(float multiplier, String multiplierName)
+ throws IllegalArgumentException {
+ if (multiplier < 0.0f || multiplier > 1.0f) {
+ throw new IllegalArgumentException("Trying to set " + multiplierName
+ + " out of bounds: " + multiplier);
+ }
+ return multiplier;
+ }
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac81b04..e9a877e6013d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2378,6 +2378,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return false;
}
+ return resumeFocusedTasksTopActivitiesUnchecked(targetRootTask, target, targetOptions,
+ deferPause);
+ }
+
+ @VisibleForTesting
+ boolean resumeFocusedTasksTopActivitiesUnchecked(
+ Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
+ boolean deferPause) {
boolean result = false;
if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
|| getTopDisplayFocusedRootTask() == targetRootTask)) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b43a4540bbde..8afcf0e1e05a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -150,7 +150,9 @@ public class WindowAnimator {
dc.checkAppWindowsReadyToShow();
}
if (accessibilityController.hasCallbacks()) {
- accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
+ accessibilityController
+ .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(
+ dc.mDisplayId);
}
if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 731184fbc39c..d340272ee2c7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -848,7 +848,12 @@ public class WindowManagerShellCommand extends ShellCommand {
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
@@ -867,7 +872,12 @@ public class WindowManagerShellCommand extends ShellCommand {
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1ad1be2ef68..8c9317a32483 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -69,6 +69,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -829,18 +830,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+ final boolean wasPrevFocusableAndVisible = tr.isFocusableAndVisible();
+
int effects = applyChanges(tr, c);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
tr.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -873,8 +876,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
boolean canEnterPip = activity.checkEnterPictureInPictureState(
"applyTaskChanges", true /* beforeStopping */);
if (canEnterPip) {
- canEnterPip = mService.mActivityClientController
- .requestPictureInPictureMode(activity);
+ mService.mTaskSupervisor.beginDeferResume();
+ try {
+ canEnterPip = mService.mActivityClientController
+ .requestPictureInPictureMode(activity);
+ } finally {
+ mService.mTaskSupervisor.endDeferResume();
+ if (canEnterPip && !isPip2ExperimentEnabled()) {
+ // Wait until the transaction is applied to only resume once.
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
}
if (!canEnterPip) {
// Restore the flag to its previous state when the activity cannot enter PIP.
@@ -883,6 +895,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
+ // Activity in this Task may resume/pause when enter/exit pip.
+ if (wasPrevFocusableAndVisible != tr.isFocusableAndVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
return effects;
}
@@ -948,7 +965,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
taskFragment.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
effects |= applyChanges(taskFragment, c);
@@ -2293,6 +2310,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
ownerActivity.getUid(), ownerActivity.info.processName);
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
+ taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+ }
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b0eee0881f63..1666fef13685 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1708,7 +1708,6 @@ public class DisplayManagerServiceTest {
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
*/
@Test
- @FlakyTest(bugId = 127687569)
public void testCreateVirtualDisplay_setSurface() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 783971a1afe4..89b48bad2358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,9 +16,18 @@
package com.android.server.am;
+import static android.os.Process.INVALID_UID;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_NULL;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_ONE_SHOT_SENT;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +43,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -54,6 +64,7 @@ public class PendingIntentControllerTest {
private static final String TEST_PACKAGE_NAME = "test-package-1";
private static final String TEST_FEATURE_ID = "test-feature-1";
private static final int TEST_CALLING_UID = android.os.Process.myUid();
+ private static final int TEST_USER_ID = 0;
private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
@Mock
@@ -92,7 +103,7 @@ public class PendingIntentControllerTest {
private PendingIntentRecord createPendingIntentRecord(int flags) {
return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
- TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0,
+ TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0,
TEST_INTENTS, null, flags, null);
}
@@ -126,6 +137,54 @@ public class PendingIntentControllerTest {
piCaptor.getValue().getTarget());
}
+ @Test
+ public void testCancellationReason() {
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ assertCancelReason(CANCEL_REASON_NULL, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.cancelIntentSender(pir);
+ assertCancelReason(CANCEL_REASON_OWNER_CANCELED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+ assertCancelReason(CANCEL_REASON_SUPERSEDED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+ pir.send(0, null, null, null, null, null, null);
+ assertCancelReason(CANCEL_REASON_ONE_SHOT_SENT, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(TEST_PACKAGE_NAME,
+ TEST_USER_ID, UserHandle.getAppId(TEST_CALLING_UID), true,
+ CANCEL_REASON_OWNER_FORCE_STOPPED);
+ assertCancelReason(CANCEL_REASON_OWNER_FORCE_STOPPED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(null,
+ TEST_USER_ID, INVALID_UID, true,
+ CANCEL_REASON_USER_STOPPED);
+ assertCancelReason(CANCEL_REASON_USER_STOPPED, pir.cancelReason);
+ }
+ }
+
+ private void assertCancelReason(int expectedReason, int actualReason) {
+ final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+ + "; Actual: " + cancelReasonToString(actualReason);
+ assertEquals(errMsg, expectedReason, actualReason);
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 4e059b4b7432..8024915692aa 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -17,7 +17,6 @@
package com.android.server;
import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
-import static com.android.server.GestureLauncherService.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -773,6 +772,9 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_triggerEmergency_fiveFastTaps_gestureIgnored() {
+ when(mResources.getInteger(
+ com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis))
+ .thenReturn(200);
// Trigger emergency by tapping button 5 times
long eventTime = triggerEmergencyGesture(/* tapIntervalMs= */ 1);
@@ -1449,7 +1451,7 @@ public class GestureLauncherServiceTest {
long emergencyGestureTapDetectionMinTimeMs = Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS,
- EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS);
+ 200);
assertTrue(intercepted);
if (tapIntervalMs * 4 > emergencyGestureTapDetectionMinTimeMs) {
assertTrue(outLaunched.value);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 1bf9a9d02431..5a1785175be7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -69,10 +69,9 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
@@ -144,8 +143,7 @@ public class AccessibilityManagerServiceTest {
ApplicationProvider.getApplicationContext());
@Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int ACTION_ID = 20;
private static final String LABEL = "label";
@@ -624,7 +622,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testOnClientChange_magnificationTwoFingerTripleTapEnabled_requestConnection() {
when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
@@ -642,7 +640,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testOnClientChange_magnificationTwoFingerTripleTapDisabled_requestDisconnection() {
when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
@@ -704,7 +702,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void onClientChange_magnificationTwoFingerTripleTapDisabled_removeMagnificationButton() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
@@ -720,7 +718,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void onClientChange_magnificationTwoFingerTripleTapEnabled_keepMagnificationButton() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
@@ -772,7 +770,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsDisabled(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+ @DisableFlags(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
public void testPerformAccessibilityShortcut_hearingAids_startActivityWithExpectedComponent() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
@@ -790,7 +788,7 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+ @EnableFlags(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
public void testPerformAccessibilityShortcut_hearingAids_sendExpectedBroadcast() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
@@ -949,7 +947,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES)
+ @EnableFlags(FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
@@ -1008,6 +1006,33 @@ public class AccessibilityManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
+ public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
+ Settings.Secure.putInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN
+ );
+
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.HARDWARE,
+ List.of(target),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN))
+ .isEqualTo(AccessibilityShortcutController.DialogStatus.SHOWN);
+ }
+
+ @Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1341,7 +1366,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
mTestableContext.getTestablePermissions().setPermission(
Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
@@ -1355,7 +1380,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
mockStatusBarServiceGranted(mTestableContext);
mTestableContext.getTestablePermissions().setPermission(
@@ -1369,7 +1394,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
mockStatusBarServiceGranted(mTestableContext);
mockManageAccessibilityGranted(mTestableContext);
@@ -1389,7 +1414,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
List<ComponentName> tiles =
@@ -1406,7 +1431,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
mockStatusBarServiceGranted(mTestableContext);
mockManageAccessibilityGranted(mTestableContext);
@@ -1424,7 +1449,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
mockStatusBarServiceGranted(mTestableContext);
mockManageAccessibilityGranted(mTestableContext);
@@ -1446,7 +1471,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
mockStatusBarServiceGranted(mTestableContext);
mockManageAccessibilityGranted(mTestableContext);
@@ -1469,7 +1494,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
@@ -1487,7 +1512,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() {
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
@@ -1510,7 +1535,7 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @RequiresFlagsDisabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() {
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 515898a883e8..e6cf0c38a10c 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -50,6 +50,7 @@ android_test {
"SettingsLib",
"libprotobuf-java-lite",
"platformprotoslite",
+ "platform-parametric-runner-lib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4a61d32d00b2..805bc172035e 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -106,22 +106,23 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
+import static com.android.server.notification.NotificationManagerService.TAG;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
-
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -130,7 +131,6 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
@@ -138,10 +138,24 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
import android.Manifest;
import android.annotation.Nullable;
@@ -168,6 +182,8 @@ import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.job.JobScheduler;
+import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -187,6 +203,7 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
@@ -217,6 +234,7 @@ import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
@@ -235,7 +253,8 @@ import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
+import android.testing.TestableContentResolver;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestablePermissions;
@@ -245,13 +264,13 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Pair;
import android.util.Xml;
+import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -259,6 +278,7 @@ import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -282,13 +302,10 @@ import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -306,6 +323,8 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -322,9 +341,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -369,6 +388,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
private PermissionHelper mPermissionHelper;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+ @Rule(order = Integer.MAX_VALUE)
+ public TestWithLooperRule mlooperRule = new TestWithLooperRule();
private TestableLooper mTestableLooper;
@Mock
private RankingHelper mRankingHelper;
@@ -415,8 +436,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
- @Mock
- IIntentSender pi1;
+ private PendingIntent mActivityIntent;
+ private PendingIntent mActivityIntentImmutable;
private static final int MAX_POST_DELAY = 1000;
@@ -465,6 +486,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
StatsManager mStatsManager;
@Mock
AlarmManager mAlarmManager;
+ @Mock JobScheduler mJobScheduler;
@Mock
MultiRateLimiter mToastRateLimiter;
BroadcastReceiver mPackageIntentReceiver;
@@ -508,6 +530,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
}
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_ALL_NOTIFS_NEED_TTL);
+ }
+
+ public NotificationManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
// Shell permisssions will override permissions of our app, so add all necessary permissions
@@ -540,12 +572,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
mContext.addMockSystemService(NotificationManager.class, mMockNm);
+ mContext.addMockSystemService(RoleManager.class, mock(RoleManager.class));
+ mContext.addMockSystemService(Context.LAUNCHER_APPS_SERVICE, mLauncherApps);
+ mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE,
+ mock(AccessibilityManager.class));
doNothing().when(mContext).sendBroadcast(any(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+ TestableContentResolver cr = mock(TestableContentResolver.class);
+ when(mContext.getContentResolver()).thenReturn(cr);
+ doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt());
setDpmAppOppsExemptFromDismissal(false);
@@ -648,11 +690,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
});
// TODO (b/291907312): remove feature flag
- // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
+ // NOTE: Prefer using the @EnableFlags annotation where possible. Do not add any android.app
// flags here.
mSetFlagsRule.disableFlags(
Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+ mActivityIntent = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), PendingIntent.FLAG_MUTABLE));
+ mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), FLAG_IMMUTABLE));
+
initNMS();
}
@@ -689,6 +736,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mPowerManager, mPostNotificationTrackerFactory);
mService.setAttentionHelper(mAttentionHelper);
+ mService.setLockPatternUtils(mock(LockPatternUtils.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
@@ -749,7 +797,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
- assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver);
+ if (!Flags.allNotifsNeedTtl()) {
+ assertNotNull("Notification timeout receiver should exist",
+ mNotificationTimeoutReceiver);
+ }
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -834,9 +885,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
if (mFile != null) mFile.delete();
clearDeviceConfig();
+ if (mActivityIntent != null) {
+ mActivityIntent.cancel();
+ }
+
+ mService.clearNotifications();
+ TestableLooper.get(this).processAllMessages();
+
try {
mService.onDestroy();
} catch (IllegalStateException | IllegalArgumentException e) {
+ Log.e(TAG, "failed to destroy", e);
// can throw if a broadcast receiver was never registered
}
@@ -846,6 +905,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// could cause issues, for example, messages that remove/cancel shown toasts (this causes
// problematic interactions with mocks when they're no longer working as expected).
mWorkerHandler.removeCallbacksAndMessages(null);
+
+ if (TestableLooper.get(this) != null) {
+ // Must remove static reference to this test object to prevent leak (b/261039202)
+ TestableLooper.remove(this);
+ }
}
private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
@@ -1005,7 +1069,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
+ .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build())
+ .addAction(new Notification.Action.Builder(
+ null, "test", mActivityIntentImmutable).build());
if (extender != null) {
nb.extend(extender);
}
@@ -1045,18 +1111,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
String tag) {
- return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+ return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false, true);
}
private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
- NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+ NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary,
+ boolean mutable) {
if (channel == null) {
channel = mTestNotificationChannel;
}
if (tag == null) {
tag = "tag";
}
- Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+ Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey,
+ isSummary, mutable);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id,
tag, mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -1129,18 +1197,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
- String groupKey, boolean isSummary) {
+ String groupKey, boolean isSummary, boolean mutable) {
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
.build();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mutable ? mActivityIntent : mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
// Make it messaging style
Notification.Builder nb = new Notification.Builder(mContext,
@@ -1167,17 +1232,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
private Notification.BubbleMetadata getBubbleMetadata() {
- PendingIntent pendingIntent = mock(PendingIntent.class);
- Intent intent = mock(Intent.class);
- when(pendingIntent.getIntent()).thenReturn(intent);
- when(pendingIntent.getTarget()).thenReturn(pi1);
-
ActivityInfo info = new ActivityInfo();
info.resizeMode = RESIZE_MODE_RESIZEABLE;
- when(intent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = info;
+ when(mPackageManagerClient.resolveActivity(any(), anyInt())).thenReturn(ri);
return new Notification.BubbleMetadata.Builder(
- pendingIntent,
+ mActivityIntent,
Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon))
.build();
}
@@ -1189,7 +1251,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Notification that has bubble metadata
NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
- mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrBubble.getSbn().getTag(),
nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
@@ -1203,7 +1266,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Notification without bubble metadata
NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrPlain.getSbn().getTag(),
nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(),
@@ -1215,7 +1279,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Summary notification for both of those
NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+ mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */,
+ true);
if (summaryAutoCancel) {
nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
@@ -1232,6 +1297,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testLimitTimeOutBroadcast() {
NotificationChannel channel = new NotificationChannel("id", "name",
NotificationManager.IMPORTANCE_HIGH);
@@ -2501,8 +2567,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelWithTagDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -2529,19 +2595,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
-
- mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
- mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
- sbn.getUserId());
- waitForIdle();
-
- assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(0);
- assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelAllDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Adds a lifetime extended notification.
final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1,
null, false);
@@ -2978,9 +3036,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 1, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -3211,9 +3269,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_byKey_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -5695,12 +5753,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mZenModeHelper).updateZenRulesOnLocaleChange();
}
- private void simulateNotificationTimeoutBroadcast(String notificationKey) {
- final Bundle extras = new Bundle();
- extras.putString(EXTRA_KEY, notificationKey);
- final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
- intent.putExtras(extras);
- mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ private void simulateNotificationTimeout(String notificationKey) {
+ if (Flags.allNotifsNeedTtl()) {
+ mService.mNotificationManagerPrivate.timeoutNotification(notificationKey);
+ } else {
+ final Bundle extras = new Bundle();
+ extras.putString(EXTRA_KEY, notificationKey);
+ final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
+ intent.putExtras(extras);
+ mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ }
}
@Test
@@ -5709,7 +5771,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 1, null, false);
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was cancelled.
@@ -5725,7 +5787,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
notif.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5741,7 +5803,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
notif.getSbn().getNotification().flags = Notification.FLAG_USER_INITIATED_JOB;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5751,15 +5813,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testTimeout_NoCancelLifetimeExtensionNotification() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Create a notification with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5839,8 +5901,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(r);
@@ -6662,6 +6724,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(visitor, times(1)).accept(eq(personIcon.getUri()));
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(hangUpUri));
+ hangUpIntent.cancel();
}
@Test
@@ -6691,6 +6754,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
verify(visitor, times(1)).accept(eq(declineUri));
+ answerIntent.cancel();
+ declineIntent.cancel();
}
@Test
@@ -6767,6 +6832,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(visitor, times(1)).accept(eq(actionIntentUri));
verify(visitor).accept(eq(wearActionIcon.getUri()));
verify(visitor, times(1)).accept(eq(wearActionIntentUri));
+ displayIntent.cancel();
+ actionIntent.cancel();
+ wearActionIntent.cancel();
}
@Test
@@ -6788,6 +6856,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(visitor, times(1)).accept(eq(contentIntentUri));
verify(visitor, times(1)).accept(eq(deleteIntentUri));
+ contentIntent.cancel();
+ deleteIntent.cancel();
}
@Test
@@ -6813,6 +6883,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(visitor, times(1)).accept(eq(readPendingIntentUri));
verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
+ readPendingIntent.cancel();
+ replyPendingIntent.cancel();
}
@Test
@@ -8587,8 +8659,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testOnNotificationSmartReplySent() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8613,8 +8685,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8647,8 +8719,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testOnNotificationActionClick() {
final int actionIndex = 2;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = false;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -8669,9 +8740,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testActionClickLifetimeExtendedCancel() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
final Notification.Action action =
new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
@@ -8797,8 +8867,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testOnAssistantNotificationActionClick() {
final int actionIndex = 1;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -9313,7 +9382,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false);
+ Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
null, mUid, 0,
@@ -9357,7 +9426,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
@@ -10615,7 +10684,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -10675,7 +10744,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
shortcutId).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(shortcutId);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11068,7 +11137,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11104,7 +11173,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11148,7 +11217,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11193,7 +11262,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
//Create notification record without a shortcutId
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11328,7 +11397,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testRecordMessages_invalidMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_invalidMsg", mUid, 0, nb.build(),
@@ -11369,7 +11438,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testRecordMessages_validMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_validMsg", mUid, 0, nb.build(),
@@ -11405,7 +11474,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
@@ -11625,10 +11694,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testImmutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false, false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11642,10 +11710,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false, true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11658,10 +11724,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testImmutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false,
+ false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11675,10 +11741,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11690,18 +11755,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testImmutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_IMMUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11722,18 +11784,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntent).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11749,10 +11806,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testImmutableActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11764,12 +11819,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testImmutableContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
- extraAction.add(new Notification.Action(0, "hello", null));
+ extraAction.add(new Notification.Action(0, "hello", mActivityIntentImmutable));
Bundle signals = new Bundle();
signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
@@ -12121,8 +12175,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCallNotificationsBypassBlock() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
Notification.Builder nb = new Notification.Builder(
@@ -12145,8 +12197,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setName("caller")
.build();
nb.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12214,8 +12266,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.clearNotifications();
reset(mUsageStats);
Person person = new Person.Builder().setName("caller").build();
- nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ nb.setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, nb.build(),
UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12709,7 +12761,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12744,7 +12796,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12760,7 +12812,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mInternalService.sendReviewPermissionsNotification();
// Notification should be sent
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
@@ -12792,7 +12844,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(permissionState);
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
+ .setFullScreenIntent(mActivityIntent, true)
.build();
mService.fixNotification(n, mPkg, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
@@ -12860,7 +12912,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -12881,7 +12933,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13040,8 +13092,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification n = new Notification.Builder(mContext, "test")
// Without FLAG_FOREGROUND_SERVICE.
//.setFlag(FLAG_FOREGROUND_SERVICE, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13057,8 +13108,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13072,9 +13122,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void checkCallStyleNotification_allowedForFsiAllowed() throws Exception {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setFullScreenIntent(mActivityIntent, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13089,8 +13138,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13178,8 +13226,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.build();
Notification n = new Notification.Builder(mContext, "test")
.setOngoing(true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
// When: fix the notification with NotificationManagerService
@@ -14338,6 +14385,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ actionIntent2.cancel();
+ actionIntent1.cancel();
}
@Test
@@ -14366,6 +14416,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ publicContentIntent.cancel();
+ actionIntent.cancel();
+ publicActionIntent.cancel();
}
@Test
@@ -14891,8 +14945,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testFixNotification_clearsLifetimeExtendedFlag() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
.build();
@@ -15321,7 +15375,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_missingTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15333,7 +15387,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_doesNotOverwriteTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15351,8 +15405,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.setSmallIcon(android.R.drawable.sym_def_app_icon);
StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
testName, mUid, 0, nb.build(), userHandle, null, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
new file mode 100644
index 000000000000..8b46c8c409d4
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.notification;
+
+import static com.android.server.notification.TimeToLiveHelper.EXTRA_KEY;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+public class TimeToLiveHelperTest extends UiServiceTestCase {
+
+ TimeToLiveHelper mHelper;
+ @Mock
+ NotificationManagerPrivate mNm;
+ @Mock
+ AlarmManager mAm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(AlarmManager.class, mAm);
+ mHelper = new TimeToLiveHelper(mNm, mContext);
+ }
+
+ @After
+ public void tearDown() {
+ mHelper.destroy();
+ }
+
+ private NotificationRecord getRecord(String tag, int timeoutAfter) {
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setTimeoutAfter(timeoutAfter);
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, tag, mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
+ @Test
+ public void testTimeout() {
+ mHelper.scheduleTimeoutLocked(getRecord("testTimeout", 1), 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+ }
+
+ @Test
+ public void testTimeoutExpires() {
+ NotificationRecord r = getRecord("testTimeoutExpires", 1);
+
+ mHelper.scheduleTimeoutLocked(r, 1);
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captor.capture());
+
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captor.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).isEmpty();
+ }
+
+ @Test
+ public void testTimeoutExpires_twoEntries() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+
+ ArgumentCaptor<PendingIntent> captorNewSet = ArgumentCaptor.forClass(PendingIntent.class);
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captorSet.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).hasSize(1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorNewSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedSecond() {
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedFirst() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+ verify(mAm, never()).cancel((PendingIntent) any());
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ }
+
+ @Test
+ public void testTimeout_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ // cancel original alarm
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // schedule later alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(4L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_twoEntries_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(later.getKey());
+
+ // "first" was canceled because it's now later
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // "later" is now the first entry, and needs the matching alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index 80e169d8d579..b90fa21cb2b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -25,6 +25,8 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_RE
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.testing.Assert.assertThrows;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.testing.Assert;
+
import org.junit.Before;
import org.junit.Test;
@@ -288,4 +292,56 @@ public class LetterboxConfigurationTest {
false /* forTabletopMode */,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
}
+
+ @Test
+ public void test_setLetterboxHorizontalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxVerticalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxBookModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxTabletopModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fbf142632c78..2e80bc721c7f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4143,31 +4143,6 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (2800, 1400).
-
- // Below 0.0.
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
-
- // Above 1.0
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
- }
-
- @Test
public void testUpdateResolvedBoundsHorizontalPosition_right() {
// Display configured as (2800, 1400).
assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -4398,31 +4373,6 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- public void testUpdateResolvedBoundsVerticalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (1400, 2800).
-
- // Below 0.0.
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
-
- // Above 1.0
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
- }
-
- @Test
public void testUpdateResolvedBoundsVerticalPosition_bottom() {
// Display configured as (1400, 2800).
assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 52485eec8505..002a3d5a0d53 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -19,6 +19,8 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1024,6 +1026,58 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_systemOrganizer() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
+ mController.unregisterOrganizer(mIOrganizer);
+ registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation should be set for a system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_nonSystemOrganizer() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation is ignored for a non-system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3c5b12c68e1c..4837fcbfc262 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -752,6 +753,21 @@ public class TaskFragmentTest extends WindowTestsBase {
}
@Test
+ public void testGetOrientation_reportOverrideOrientation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragmentWithActivity(task);
+ final ActivityRecord activity = tf.getTopMostActivity();
+ tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND);
+
+ // Should report the override orientation
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+ // Should report the override orientation even if the activity requests a different value
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+ }
+
+ @Test
public void testUpdateImeParentForActivityEmbedding() {
// Setup two activities in ActivityEmbedding.
final Task task = createTask(mDisplayContent);
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 ec2c968a8a0a..50db99ea6dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1162,7 +1162,8 @@ public class WindowManagerServiceTests extends WindowTestsBase {
invocationOnMock.callRealMethod();
return null;
}).when(surface).lockCanvas(any());
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
+ mWm.mAccessibilityController
+ .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
waitUntilHandlersIdle();
try {
verify(surface).lockCanvas(any());
@@ -1170,7 +1171,8 @@ public class WindowManagerServiceTests extends WindowTestsBase {
clearInvocations(surface);
// Invalidate and redraw.
mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
+ mWm.mAccessibilityController
+ .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
// Turn off magnification to release surface.
mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 43b424fab907..69b5c37466da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1681,7 +1681,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
clearInvocations(mWm.mAtmService.mRootWindowContainer);
// The token for the PIP root task may have changed when the task entered PIP mode, so do
@@ -1690,7 +1691,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
record.getRootTask().mRemoteToken.toWindowContainerToken();
t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
}
@Test
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c7317735bc7..a43cd39f0dcb 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@ android_app {
platform_apis: true,
certificate: "platform",
libs: ["org.apache.http.legacy"],
+ optional_uses_libs: ["org.apache.http.legacy"],
optimize: {
enabled: false,
},
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index b98161dd26fb..191f38d3df80 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -65,8 +65,10 @@ public class AslConverter {
return new AndroidSafetyLabelFactory()
.createFromHrElements(XmlUtils.listOf(appMetadataBundles));
case ON_DEVICE:
- throw new IllegalArgumentException(
- "Parsing from on-device format is not supported at this time.");
+ Element bundleEle =
+ XmlUtils.getSingleChildElement(document, XmlUtils.OD_TAG_BUNDLE, true);
+ return new AndroidSafetyLabelFactory()
+ .createFromOdElements(XmlUtils.listOf(bundleEle));
default:
throw new IllegalStateException("Unrecognized input format.");
}
@@ -89,8 +91,10 @@ public class AslConverter {
switch (format) {
case HUMAN_READABLE:
- throw new IllegalArgumentException(
- "Outputting human-readable format is not supported at this time.");
+ for (var child : asl.toHrDomElements(document)) {
+ document.appendChild(child);
+ }
+ break;
case ON_DEVICE:
for (var child : asl.toOdDomElements(document)) {
document.appendChild(child);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index ecfad91a378f..72140a17297c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -65,6 +65,17 @@ public class AndroidSafetyLabel implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element aslEle = doc.createElement(XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+ aslEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion));
+ if (mSafetyLabels != null) {
+ XmlUtils.appendChildren(aslEle, mSafetyLabels.toHrDomElements(doc));
+ }
+ if (mSystemAppSafetyLabel != null) {
+ XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toHrDomElements(doc));
+ }
+ if (mTransparencyInfo != null) {
+ XmlUtils.appendChildren(aslEle, mTransparencyInfo.toHrDomElements(doc));
+ }
+ return XmlUtils.listOf(aslEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index 41ce6e55e989..c53cbbf99a46 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -56,10 +56,32 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android
version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
}
- /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
+ /** Creates an {@link AndroidSafetyLabel} from on-device DOM elements */
@Override
public AndroidSafetyLabel createFromOdElements(List<Element> elements)
throws MalformedXmlException {
- return null;
+ Element bundleEle = XmlUtils.getSingleElement(elements);
+ Long version = XmlUtils.getOdLongEle(bundleEle, XmlUtils.OD_NAME_VERSION, true);
+
+ Element safetyLabelsEle =
+ XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_SAFETY_LABELS, false);
+ SafetyLabels safetyLabels =
+ new SafetyLabelsFactory().createFromOdElements(XmlUtils.listOf(safetyLabelsEle));
+
+ Element systemAppSafetyLabelEle =
+ XmlUtils.getOdPbundleWithName(
+ bundleEle, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL, false);
+ SystemAppSafetyLabel systemAppSafetyLabel =
+ new SystemAppSafetyLabelFactory()
+ .createFromOdElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+
+ Element transparencyInfoEle =
+ XmlUtils.getOdPbundleWithName(bundleEle, XmlUtils.OD_NAME_TRANSPARENCY_INFO, false);
+ TransparencyInfo transparencyInfo =
+ new TransparencyInfoFactory()
+ .createFromOdElements(XmlUtils.listOf(transparencyInfoEle));
+
+ return new AndroidSafetyLabel(
+ version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index 21f328d8e2d0..129733ebc1b6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -146,6 +146,55 @@ public class AppInfo implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element appInfoEle = doc.createElement(XmlUtils.HR_TAG_APP_INFO);
+ if (this.mTitle != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_TITLE, this.mTitle);
+ }
+ if (this.mDescription != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_DESCRIPTION, this.mDescription);
+ }
+ if (this.mContainsAds != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_CONTAINS_ADS, String.valueOf(this.mContainsAds));
+ }
+ if (this.mObeyAps != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_OBEY_APS, String.valueOf(this.mObeyAps));
+ }
+ if (this.mAdsFingerprinting != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_ADS_FINGERPRINTING, String.valueOf(this.mAdsFingerprinting));
+ }
+ if (this.mSecurityFingerprinting != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING,
+ String.valueOf(this.mSecurityFingerprinting));
+ }
+ if (this.mPrivacyPolicy != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_PRIVACY_POLICY, this.mPrivacyPolicy);
+ }
+ if (this.mSecurityEndpoints != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, String.join("|", this.mSecurityEndpoints));
+ }
+ if (this.mFirstPartyEndpoints != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS,
+ String.join("|", this.mFirstPartyEndpoints));
+ }
+ if (this.mServiceProviderEndpoints != null) {
+ appInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS,
+ String.join("|", this.mServiceProviderEndpoints));
+ }
+ if (this.mCategory != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_CATEGORY, this.mCategory);
+ }
+ if (this.mEmail != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, this.mEmail);
+ }
+ if (this.mWebsite != null) {
+ appInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, this.mWebsite);
+ }
+ return XmlUtils.listOf(appInfoEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 6fcf637fe069..c5069619e069 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -35,15 +35,16 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
return null;
}
- String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE);
- String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION);
+ String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE, true);
+ String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION, true);
Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, true);
Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, true);
Boolean adsFingerprinting =
XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, true);
Boolean securityFingerprinting =
XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, true);
- String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY);
+ String privacyPolicy =
+ XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, true);
List<String> securityEndpoints =
XmlUtils.getPipelineSplitAttr(
appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, true);
@@ -53,8 +54,8 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
List<String> serviceProviderEndpoints =
XmlUtils.getPipelineSplitAttr(
appInfoEle, XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, true);
- String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY);
- String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL);
+ String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY, true);
+ String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
return new AppInfo(
@@ -76,6 +77,48 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@Override
public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
- return null;
+ Element appInfoEle = XmlUtils.getSingleElement(elements);
+ if (appInfoEle == null) {
+ AslgenUtil.logI("No AppInfo found in od format.");
+ return null;
+ }
+
+ String title = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_TITLE, true);
+ String description =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DESCRIPTION, true);
+ Boolean containsAds =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_CONTAINS_ADS, true);
+ Boolean obeyAps = XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_OBEY_APS, true);
+ Boolean adsFingerprinting =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_ADS_FINGERPRINTING, true);
+ Boolean securityFingerprinting =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, true);
+ String privacyPolicy =
+ XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, true);
+ List<String> securityEndpoints =
+ XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_SECURITY_ENDPOINT, true);
+ List<String> firstPartyEndpoints =
+ XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT, true);
+ List<String> serviceProviderEndpoints =
+ XmlUtils.getOdStringArray(
+ appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT, true);
+ String category = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_CATEGORY, true);
+ String email = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_EMAIL, true);
+ String website = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
+
+ return new AppInfo(
+ title,
+ description,
+ containsAds,
+ obeyAps,
+ adsFingerprinting,
+ securityFingerprinting,
+ privacyPolicy,
+ securityEndpoints,
+ firstPartyEndpoints,
+ serviceProviderEndpoints,
+ category,
+ email,
+ website);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index eb975540ce70..d551953477d8 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -63,6 +63,8 @@ public class DataCategory implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ throw new IllegalStateException(
+ "Turning DataCategory or DataType into human-readable DOM elements requires"
+ + " visibility into parent elements. The logic resides in DataLabels.");
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index 02b7189c09ba..97304cb36081 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -163,7 +163,9 @@ public class DataType implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ throw new IllegalStateException(
+ "Turning DataCategory or DataType into human-readable DOM elements requires"
+ + " visibility into parent elements. The logic resides in DataLabels.");
}
private static void maybeAddBoolToOdElement(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
index efdc8d0a5f11..94fad9607880 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -143,6 +143,31 @@ public class DeveloperInfo implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element developerInfoEle = doc.createElement(XmlUtils.HR_TAG_DEVELOPER_INFO);
+ if (mName != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_NAME, mName);
+ }
+ if (mEmail != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, mEmail);
+ }
+ if (mAddress != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_ADDRESS, mAddress);
+ }
+ if (mCountryRegion != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_COUNTRY_REGION, mCountryRegion);
+ }
+ if (mDeveloperRelationship != null) {
+ developerInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, mDeveloperRelationship.toString());
+ }
+ if (mWebsite != null) {
+ developerInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, mWebsite);
+ }
+ if (mAppDeveloperRegistryId != null) {
+ developerInfoEle.setAttribute(
+ XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, mAppDeveloperRegistryId);
+ }
+
+ return XmlUtils.listOf(developerInfoEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
index c3e7ac35c545..0f3b41cd5d1a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -34,15 +34,15 @@ public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInf
AslgenUtil.logI("No DeveloperInfo found in hr format.");
return null;
}
- String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME);
- String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL);
- String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS);
+ String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME, true);
+ String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
+ String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS, true);
String countryRegion =
- XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION);
+ XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION, true);
DeveloperInfo.DeveloperRelationship developerRelationship =
DeveloperInfo.DeveloperRelationship.forString(
XmlUtils.getStringAttr(
- developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP));
+ developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, true));
String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
String appDeveloperRegistryId =
XmlUtils.getStringAttr(
@@ -61,6 +61,36 @@ public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInf
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@Override
public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
- return null;
+ Element developerInfoEle = XmlUtils.getSingleElement(elements);
+ if (developerInfoEle == null) {
+ AslgenUtil.logI("No DeveloperInfo found in od format.");
+ return null;
+ }
+ String name = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_NAME, true);
+ String email = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_EMAIL, true);
+ String address = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_ADDRESS, true);
+ String countryRegion =
+ XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_COUNTRY_REGION, true);
+ DeveloperInfo.DeveloperRelationship developerRelationship =
+ DeveloperInfo.DeveloperRelationship.forValue(
+ (int)
+ (long)
+ XmlUtils.getOdLongEle(
+ developerInfoEle,
+ XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+ true));
+ String website = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
+ String appDeveloperRegistryId =
+ XmlUtils.getOdStringEle(
+ developerInfoEle, XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, false);
+
+ return new DeveloperInfo(
+ name,
+ email,
+ address,
+ countryRegion,
+ developerRelationship,
+ website,
+ appDeveloperRegistryId);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 576820dac6c6..6af80715f7c1 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -74,6 +74,18 @@ public class SafetyLabels implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element safetyLabelsEle = doc.createElement(XmlUtils.HR_TAG_SAFETY_LABELS);
+ safetyLabelsEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion));
+
+ if (mDataLabels != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toHrDomElements(doc));
+ }
+ if (mSecurityLabels != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toHrDomElements(doc));
+ }
+ if (mThirdPartyVerification != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toHrDomElements(doc));
+ }
+ return XmlUtils.listOf(safetyLabelsEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 7e1838f40680..2644b435311b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -66,6 +66,37 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@Override
public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException {
- return null;
+ Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
+ if (safetyLabelsEle == null) {
+ AslgenUtil.logI("No SafetyLabels found in od format.");
+ return null;
+ }
+ Long version = XmlUtils.getOdLongEle(safetyLabelsEle, XmlUtils.OD_NAME_VERSION, true);
+
+ DataLabels dataLabels =
+ new DataLabelsFactory()
+ .createFromOdElements(
+ XmlUtils.listOf(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_DATA_LABELS,
+ false)));
+ SecurityLabels securityLabels =
+ new SecurityLabelsFactory()
+ .createFromOdElements(
+ XmlUtils.listOf(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_SECURITY_LABELS,
+ false)));
+ ThirdPartyVerification thirdPartyVerification =
+ new ThirdPartyVerificationFactory()
+ .createFromOdElements(
+ XmlUtils.listOf(
+ XmlUtils.getOdPbundleWithName(
+ safetyLabelsEle,
+ XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION,
+ false)));
+ return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
index 437343b14605..48643ba0e3ab 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java
@@ -54,6 +54,13 @@ public class SecurityLabels implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element ele = doc.createElement(XmlUtils.HR_TAG_SECURITY_LABELS);
+ if (mIsDataDeletable != null) {
+ ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_DELETABLE, String.valueOf(mIsDataDeletable));
+ }
+ if (mIsDataEncrypted != null) {
+ ele.setAttribute(XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, String.valueOf(mIsDataEncrypted));
+ }
+ return XmlUtils.listOf(ele);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
index 9dc4712c33b0..525a80388261 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java
@@ -46,6 +46,15 @@ public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLab
@Override
public SecurityLabels createFromOdElements(List<Element> elements)
throws MalformedXmlException {
- return null;
+ Element ele = XmlUtils.getSingleElement(elements);
+ if (ele == null) {
+ AslgenUtil.logI("No SecurityLabels found in od format.");
+ return null;
+ }
+ Boolean isDataDeletable =
+ XmlUtils.getOdBoolEle(ele, XmlUtils.OD_NAME_IS_DATA_DELETABLE, false);
+ Boolean isDataEncrypted =
+ XmlUtils.getOdBoolEle(ele, XmlUtils.OD_NAME_IS_DATA_ENCRYPTED, false);
+ return new SecurityLabels(isDataDeletable, isDataEncrypted);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index f0ecf93f2805..854c0d0ac3e1 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -50,6 +50,9 @@ public class SystemAppSafetyLabel implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element systemAppSafetyLabelEle =
+ doc.createElement(XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL);
+ systemAppSafetyLabelEle.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
+ return XmlUtils.listOf(systemAppSafetyLabelEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index 5b7fe32f2735..c8e22b6c42cd 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -36,7 +36,7 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste
return null;
}
- String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL);
+ String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL, true);
return new SystemAppSafetyLabel(url);
}
@@ -44,6 +44,12 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste
@Override
public SystemAppSafetyLabel createFromOdElements(List<Element> elements)
throws MalformedXmlException {
- return null;
+ Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
+ if (systemAppSafetyLabelEle == null) {
+ AslgenUtil.logI("No SystemAppSafetyLabel found in od format.");
+ return null;
+ }
+ String url = XmlUtils.getOdStringEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_URL, true);
+ return new SystemAppSafetyLabel(url);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
index 229b00243e0a..d74f3f062513 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java
@@ -44,6 +44,8 @@ public class ThirdPartyVerification implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element ele = doc.createElement(XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION);
+ ele.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
+ return XmlUtils.listOf(ele);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
index ac4d3836bcbd..197e7aa77743 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java
@@ -37,7 +37,7 @@ public class ThirdPartyVerificationFactory
return null;
}
- String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL);
+ String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL, true);
return new ThirdPartyVerification(url);
}
@@ -45,6 +45,13 @@ public class ThirdPartyVerificationFactory
@Override
public ThirdPartyVerification createFromOdElements(List<Element> elements)
throws MalformedXmlException {
- return null;
+ Element ele = XmlUtils.getSingleElement(elements);
+ if (ele == null) {
+ AslgenUtil.logI("No ThirdPartyVerification found in od format.");
+ return null;
+ }
+
+ String url = XmlUtils.getOdStringEle(ele, XmlUtils.OD_NAME_URL, true);
+ return new ThirdPartyVerification(url);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index ce7ef16ea54e..6a8700a10d3f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -61,6 +61,13 @@ public class TransparencyInfo implements AslMarshallable {
/** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
@Override
public List<Element> toHrDomElements(Document doc) {
- return List.of();
+ Element transparencyInfoEle = doc.createElement(XmlUtils.HR_TAG_TRANSPARENCY_INFO);
+ if (mDeveloperInfo != null) {
+ XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toHrDomElements(doc));
+ }
+ if (mAppInfo != null) {
+ XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toHrDomElements(doc));
+ }
+ return XmlUtils.listOf(transparencyInfoEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 123de01e57ba..94c564087918 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -54,6 +54,23 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare
@Override
public TransparencyInfo createFromOdElements(List<Element> elements)
throws MalformedXmlException {
- return null;
+ Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
+ if (transparencyInfoEle == null) {
+ AslgenUtil.logI("No TransparencyInfo found in od format.");
+ return null;
+ }
+
+ Element developerInfoEle =
+ XmlUtils.getOdPbundleWithName(
+ transparencyInfoEle, XmlUtils.OD_NAME_DEVELOPER_INFO, false);
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromOdElements(XmlUtils.listOf(developerInfoEle));
+
+ Element appInfoEle =
+ XmlUtils.getOdPbundleWithName(
+ transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, false);
+ AppInfo appInfo = new AppInfoFactory().createFromOdElements(XmlUtils.listOf(appInfoEle));
+
+ return new TransparencyInfo(developerInfo, appInfo);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 4f21b0c0ffad..1d54ead0a28d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -320,6 +320,63 @@ public class XmlUtils {
return b;
}
+ /** Gets an on-device Long attribute. */
+ public static Long getOdLongEle(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> longEles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_LONG).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (longEles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (longEles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return null;
+ }
+ Element longEle = longEles.get(0);
+ Long l = null;
+ try {
+ l = Long.parseLong(longEle.getAttribute(XmlUtils.OD_ATTR_VALUE));
+ } catch (NumberFormatException e) {
+ throw new MalformedXmlException(
+ String.format(
+ "%s in %s was not formatted as long", nameName, ele.getTagName()));
+ }
+ return l;
+ }
+
+ /** Gets an on-device String attribute. */
+ public static String getOdStringEle(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> eles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (eles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (eles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return null;
+ }
+ String str = eles.get(0).getAttribute(XmlUtils.OD_ATTR_VALUE);
+ if (XmlUtils.isNullOrEmpty(str) && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "%s in %s was empty or missing value", nameName, ele.getTagName()));
+ }
+ return str;
+ }
+
/** Gets a OD Pbundle Element attribute with the specified name. */
public static Element getOdPbundleWithName(Element ele, String nameName, boolean required)
throws MalformedXmlException {
@@ -379,7 +436,7 @@ public class XmlUtils {
throw new MalformedXmlException(
String.format("Found no %s in %s.", nameName, ele.getTagName()));
}
- return List.of();
+ return null;
}
Element intArrayEle = intArrayEles.get(0);
List<Element> itemEles = XmlUtils.getChildrenByTagName(intArrayEle, XmlUtils.OD_TAG_ITEM);
@@ -390,6 +447,33 @@ public class XmlUtils {
return ints;
}
+ /** Gets on-device style String array. */
+ public static List<String> getOdStringArray(Element ele, String nameName, boolean required)
+ throws MalformedXmlException {
+ List<Element> arrayEles =
+ XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_STRING_ARRAY).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName))
+ .toList();
+ if (arrayEles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+ }
+ if (arrayEles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", nameName, ele.getTagName()));
+ }
+ return null;
+ }
+ Element arrayEle = arrayEles.get(0);
+ List<Element> itemEles = XmlUtils.getChildrenByTagName(arrayEle, XmlUtils.OD_TAG_ITEM);
+ List<String> strs = new ArrayList<String>();
+ for (Element itemEle : itemEles) {
+ strs.add(XmlUtils.getStringAttr(itemEle, XmlUtils.OD_ATTR_VALUE, true));
+ }
+ return strs;
+ }
+
/**
* Utility method for making a List from one element, to support easier refactoring if needed.
* For example, List.of() doesn't support null elements.
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index e2588d7bb3e7..d2e0fc338243 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -56,18 +56,35 @@ public class AslgenTests {
InputStream hrStream =
getClass().getClassLoader().getResourceAsStream(hrPath.toString());
- String hrContents = new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
+ String hrContents =
+ TestUtils.getFormattedXml(
+ new String(hrStream.readAllBytes(), StandardCharsets.UTF_8), false);
InputStream odStream =
getClass().getClassLoader().getResourceAsStream(odPath.toString());
- String odContents = new String(odStream.readAllBytes(), StandardCharsets.UTF_8);
- AndroidSafetyLabel asl =
+ String odContents =
+ TestUtils.getFormattedXml(
+ new String(odStream.readAllBytes(), StandardCharsets.UTF_8), false);
+ AndroidSafetyLabel aslFromHr =
AslConverter.readFromString(hrContents, AslConverter.Format.HUMAN_READABLE);
- String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
- System.out.println("out: " + out);
+ String aslToOdStr =
+ TestUtils.getFormattedXml(
+ AslConverter.getXmlAsString(aslFromHr, AslConverter.Format.ON_DEVICE),
+ false);
+ AndroidSafetyLabel aslFromOd =
+ AslConverter.readFromString(odContents, AslConverter.Format.ON_DEVICE);
+ String aslToHrStr =
+ TestUtils.getFormattedXml(
+ AslConverter.getXmlAsString(
+ aslFromOd, AslConverter.Format.HUMAN_READABLE),
+ false);
- assertEquals(
- TestUtils.getFormattedXml(out, false),
- TestUtils.getFormattedXml(odContents, false));
+ System.out.println("od expected: " + odContents);
+ System.out.println("asl to od: " + aslToOdStr);
+ assertEquals(odContents, aslToOdStr);
+
+ System.out.println("hr expected: " + hrContents);
+ System.out.println("asl to hr: " + aslToHrStr);
+ assertEquals(hrContents, aslToHrStr);
}
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
index 013700728e50..61a78232801c 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class AndroidSafetyLabelTest {
@@ -38,12 +37,9 @@ public class AndroidSafetyLabelTest {
"with-system-app-safety-label.xml";
private static final String WITH_TRANSPARENCY_INFO_FILE_NAME = "with-transparency-info.xml";
- private Document mDoc = null;
-
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for android safety label missing version. */
@@ -51,6 +47,7 @@ public class AndroidSafetyLabelTest {
public void testAndroidSafetyLabelMissingVersion() throws Exception {
System.out.println("starting testAndroidSafetyLabelMissingVersion.");
hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ odToHrExpectException(MISSING_VERSION_FILE_NAME);
}
/** Test for android safety label valid empty. */
@@ -58,6 +55,7 @@ public class AndroidSafetyLabelTest {
public void testAndroidSafetyLabelValidEmptyFile() throws Exception {
System.out.println("starting testAndroidSafetyLabelValidEmptyFile.");
testHrToOdAndroidSafetyLabel(VALID_EMPTY_FILE_NAME);
+ testOdToHrAndroidSafetyLabel(VALID_EMPTY_FILE_NAME);
}
/** Test for android safety label with safety labels. */
@@ -65,6 +63,7 @@ public class AndroidSafetyLabelTest {
public void testAndroidSafetyLabelWithSafetyLabels() throws Exception {
System.out.println("starting testAndroidSafetyLabelWithSafetyLabels.");
testHrToOdAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
+ testOdToHrAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
}
/** Test for android safety label with system app safety label. */
@@ -72,6 +71,7 @@ public class AndroidSafetyLabelTest {
public void testAndroidSafetyLabelWithSystemAppSafetyLabel() throws Exception {
System.out.println("starting testAndroidSafetyLabelWithSystemAppSafetyLabel.");
testHrToOdAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
+ testOdToHrAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
}
/** Test for android safety label with transparency info. */
@@ -79,6 +79,7 @@ public class AndroidSafetyLabelTest {
public void testAndroidSafetyLabelWithTransparencyInfo() throws Exception {
System.out.println("starting testAndroidSafetyLabelWithTransparencyInfo.");
testHrToOdAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
+ testOdToHrAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
}
private void hrToOdExpectException(String fileName) {
@@ -86,12 +87,26 @@ public class AndroidSafetyLabelTest {
new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_HR_PATH, fileName);
}
+ private void odToHrExpectException(String fileName) {
+ TestUtils.odToHrExpectException(
+ new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_OD_PATH, fileName);
+ }
+
private void testHrToOdAndroidSafetyLabel(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new AndroidSafetyLabelFactory(),
ANDROID_SAFETY_LABEL_HR_PATH,
ANDROID_SAFETY_LABEL_OD_PATH,
fileName);
}
+
+ private void testOdToHrAndroidSafetyLabel(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new AndroidSafetyLabelFactory(),
+ ANDROID_SAFETY_LABEL_OD_PATH,
+ ANDROID_SAFETY_LABEL_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index a015e2eacac5..9e91c6f22641 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -25,7 +25,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
import java.nio.file.Paths;
import java.util.List;
@@ -48,19 +47,31 @@ public class AppInfoTest {
"serviceProviderEndpoints",
"category",
"email");
+ public static final List<String> REQUIRED_FIELD_NAMES_OD =
+ List.of(
+ "title",
+ "description",
+ "contains_ads",
+ "obey_aps",
+ "ads_fingerprinting",
+ "security_fingerprinting",
+ "privacy_policy",
+ "security_endpoint",
+ "first_party_endpoint",
+ "service_provider_endpoint",
+ "category",
+ "email");
public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website");
+ public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of("website");
private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for all fields valid. */
@@ -68,6 +79,7 @@ public class AppInfoTest {
public void testAllFieldsValid() throws Exception {
System.out.println("starting testAllFieldsValid.");
testHrToOdAppInfo(ALL_FIELDS_VALID_FILE_NAME);
+ testOdToHrAppInfo(ALL_FIELDS_VALID_FILE_NAME);
}
/** Tests missing required fields fails. */
@@ -75,7 +87,7 @@ public class AppInfoTest {
public void testMissingRequiredFields() throws Exception {
System.out.println("Starting testMissingRequiredFields");
for (String reqField : REQUIRED_FIELD_NAMES) {
- System.out.println("testing missing required field: " + reqField);
+ System.out.println("testing missing required field hr: " + reqField);
var appInfoEle =
TestUtils.getElementsFromResource(
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
@@ -85,6 +97,17 @@ public class AppInfoTest {
MalformedXmlException.class,
() -> new AppInfoFactory().createFromHrElements(appInfoEle));
}
+
+ for (String reqField : REQUIRED_FIELD_NAMES_OD) {
+ System.out.println("testing missing required field od: " + reqField);
+ var appInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+ }
}
/** Tests missing optional fields passes. */
@@ -96,12 +119,34 @@ public class AppInfoTest {
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.get(0).removeAttribute(optField);
AppInfo appInfo = new AppInfoFactory().createFromHrElements(ele);
- appInfo.toOdDomElements(mDoc);
+ appInfo.toOdDomElements(TestUtils.document());
+ }
+
+ for (String optField : OPTIONAL_FIELD_NAMES_OD) {
+ var ele =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(ele.get(0), optField);
+ AppInfo appInfo = new AppInfoFactory().createFromOdElements(ele);
+ appInfo.toHrDomElements(TestUtils.document());
}
}
private void testHrToOdAppInfo(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc, new AppInfoFactory(), APP_INFO_HR_PATH, APP_INFO_OD_PATH, fileName);
+ TestUtils.document(),
+ new AppInfoFactory(),
+ APP_INFO_HR_PATH,
+ APP_INFO_OD_PATH,
+ fileName);
+ }
+
+ private void testOdToHrAppInfo(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new AppInfoFactory(),
+ APP_INFO_OD_PATH,
+ APP_INFO_HR_PATH,
+ fileName);
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
index 822f1753f662..ebb31865843f 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class DataCategoryTest {
@@ -57,15 +56,12 @@ public class DataCategoryTest {
"data-category-personal-unrecognized-type.xml";
private static final String UNRECOGNIZED_CATEGORY_FILE_NAME = "data-category-unrecognized.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for data category personal. */
@@ -207,7 +203,7 @@ public class DataCategoryTest {
private void testHrToOdDataCategory(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new DataCategoryFactory(),
DATA_CATEGORY_HR_PATH,
DATA_CATEGORY_OD_PATH,
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index 6f6f2545a5d2..26617264b2e9 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class DataLabelsTest {
@@ -65,12 +64,9 @@ public class DataLabelsTest {
private static final String UNRECOGNIZED_FILE_NAME = "data-category-unrecognized.xml";
private static final String UNRECOGNIZED_TYPE_FILE_NAME = "data-category-unrecognized-type.xml";
- private Document mDoc = null;
-
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for data labels accessed valid bool. */
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
index ff8346a526ad..72e8d654b542 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -25,7 +25,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
import java.nio.file.Paths;
import java.util.List;
@@ -36,19 +35,20 @@ public class DeveloperInfoTest {
private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od";
public static final List<String> REQUIRED_FIELD_NAMES =
List.of("address", "countryRegion", "email", "name", "relationship");
+ public static final List<String> REQUIRED_FIELD_NAMES_OD =
+ List.of("address", "country_region", "email", "name", "relationship");
public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId");
+ public static final List<String> OPTIONAL_FIELD_NAMES_OD =
+ List.of("website", "app_developer_registry_id");
private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for all fields valid. */
@@ -56,6 +56,7 @@ public class DeveloperInfoTest {
public void testAllFieldsValid() throws Exception {
System.out.println("starting testAllFieldsValid.");
testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
+ testOdToHrDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
}
/** Tests missing required fields fails. */
@@ -73,6 +74,18 @@ public class DeveloperInfoTest {
MalformedXmlException.class,
() -> new DeveloperInfoFactory().createFromHrElements(developerInfoEle));
}
+
+ for (String reqField : REQUIRED_FIELD_NAMES_OD) {
+ System.out.println("testing missing required field od: " + reqField);
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), reqField);
+
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new DeveloperInfoFactory().createFromOdElements(developerInfoEle));
+ }
}
/** Tests missing optional fields passes. */
@@ -85,16 +98,35 @@ public class DeveloperInfoTest {
developerInfoEle.get(0).removeAttribute(optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromHrElements(developerInfoEle);
- developerInfo.toOdDomElements(mDoc);
+ developerInfo.toOdDomElements(TestUtils.document());
+ }
+
+ for (String optField : OPTIONAL_FIELD_NAMES_OD) {
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), optField);
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromOdElements(developerInfoEle);
+ developerInfo.toHrDomElements(TestUtils.document());
}
}
private void testHrToOdDeveloperInfo(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new DeveloperInfoFactory(),
DEVELOPER_INFO_HR_PATH,
DEVELOPER_INFO_OD_PATH,
fileName);
}
+
+ private void testOdToHrDeveloperInfo(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new DeveloperInfoFactory(),
+ DEVELOPER_INFO_OD_PATH,
+ DEVELOPER_INFO_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index c52d6c873646..bba6b548beaf 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class SafetyLabelsTest {
@@ -36,12 +35,9 @@ public class SafetyLabelsTest {
private static final String WITH_THIRD_PARTY_VERIFICATION_FILE_NAME =
"with-third-party-verification.xml";
- private Document mDoc = null;
-
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for safety labels missing version. */
@@ -49,6 +45,7 @@ public class SafetyLabelsTest {
public void testSafetyLabelsMissingVersion() throws Exception {
System.out.println("starting testSafetyLabelsMissingVersion.");
hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ odToHrExpectException(MISSING_VERSION_FILE_NAME);
}
/** Test for safety labels valid empty. */
@@ -56,6 +53,7 @@ public class SafetyLabelsTest {
public void testSafetyLabelsValidEmptyFile() throws Exception {
System.out.println("starting testSafetyLabelsValidEmptyFile.");
testHrToOdSafetyLabels(VALID_EMPTY_FILE_NAME);
+ testOdToHrSafetyLabels(VALID_EMPTY_FILE_NAME);
}
/** Test for safety labels with data labels. */
@@ -63,6 +61,7 @@ public class SafetyLabelsTest {
public void testSafetyLabelsWithDataLabels() throws Exception {
System.out.println("starting testSafetyLabelsWithDataLabels.");
testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
+ testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
}
/** Test for safety labels with security labels. */
@@ -70,6 +69,7 @@ public class SafetyLabelsTest {
public void testSafetyLabelsWithSecurityLabels() throws Exception {
System.out.println("starting testSafetyLabelsWithSecurityLabels.");
testHrToOdSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
+ testOdToHrSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
}
/** Test for safety labels with third party verification. */
@@ -77,18 +77,32 @@ public class SafetyLabelsTest {
public void testSafetyLabelsWithThirdPartyVerification() throws Exception {
System.out.println("starting testSafetyLabelsWithThirdPartyVerification.");
testHrToOdSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
+ testOdToHrSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
}
private void hrToOdExpectException(String fileName) {
TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
}
+ private void odToHrExpectException(String fileName) {
+ TestUtils.odToHrExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_OD_PATH, fileName);
+ }
+
private void testHrToOdSafetyLabels(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new SafetyLabelsFactory(),
SAFETY_LABELS_HR_PATH,
SAFETY_LABELS_OD_PATH,
fileName);
}
+
+ private void testOdToHrSafetyLabels(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new SafetyLabelsFactory(),
+ SAFETY_LABELS_OD_PATH,
+ SAFETY_LABELS_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
index c0d0d728f762..a940bc63c685 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -23,7 +23,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
import java.nio.file.Paths;
import java.util.List;
@@ -35,18 +34,17 @@ public class SecurityLabelsTest {
public static final List<String> OPTIONAL_FIELD_NAMES =
List.of("isDataDeletable", "isDataEncrypted");
+ public static final List<String> OPTIONAL_FIELD_NAMES_OD =
+ List.of("is_data_deletable", "is_data_encrypted");
private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for all fields valid. */
@@ -54,6 +52,7 @@ public class SecurityLabelsTest {
public void testAllFieldsValid() throws Exception {
System.out.println("starting testAllFieldsValid.");
testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
+ testOdToHrSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
}
/** Tests missing optional fields passes. */
@@ -65,16 +64,33 @@ public class SecurityLabelsTest {
Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.get(0).removeAttribute(optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElements(ele);
- securityLabels.toOdDomElements(mDoc);
+ securityLabels.toOdDomElements(TestUtils.document());
+ }
+ for (String optField : OPTIONAL_FIELD_NAMES_OD) {
+ var ele =
+ TestUtils.getElementsFromResource(
+ Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(ele.get(0), optField);
+ SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElements(ele);
+ securityLabels.toHrDomElements(TestUtils.document());
}
}
private void testHrToOdSecurityLabels(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new SecurityLabelsFactory(),
SECURITY_LABELS_HR_PATH,
SECURITY_LABELS_OD_PATH,
fileName);
}
+
+ private void testOdToHrSecurityLabels(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new SecurityLabelsFactory(),
+ SECURITY_LABELS_OD_PATH,
+ SECURITY_LABELS_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
index 191091a9e187..33c276487c64 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class SystemAppSafetyLabelTest {
@@ -34,15 +33,12 @@ public class SystemAppSafetyLabelTest {
private static final String VALID_FILE_NAME = "valid.xml";
private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for valid. */
@@ -50,6 +46,7 @@ public class SystemAppSafetyLabelTest {
public void testValid() throws Exception {
System.out.println("starting testValid.");
testHrToOdSystemAppSafetyLabel(VALID_FILE_NAME);
+ testOdToHrSystemAppSafetyLabel(VALID_FILE_NAME);
}
/** Tests missing url. */
@@ -57,6 +54,7 @@ public class SystemAppSafetyLabelTest {
public void testMissingUrl() throws Exception {
System.out.println("starting testMissingUrl.");
hrToOdExpectException(MISSING_URL_FILE_NAME);
+ odToHrExpectException(MISSING_URL_FILE_NAME);
}
private void hrToOdExpectException(String fileName) {
@@ -64,12 +62,26 @@ public class SystemAppSafetyLabelTest {
new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName);
}
+ private void odToHrExpectException(String fileName) {
+ TestUtils.odToHrExpectException(
+ new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_OD_PATH, fileName);
+ }
+
private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new SystemAppSafetyLabelFactory(),
SYSTEM_APP_SAFETY_LABEL_HR_PATH,
SYSTEM_APP_SAFETY_LABEL_OD_PATH,
fileName);
}
+
+ private void testOdToHrSystemAppSafetyLabel(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new SystemAppSafetyLabelFactory(),
+ SYSTEM_APP_SAFETY_LABEL_OD_PATH,
+ SYSTEM_APP_SAFETY_LABEL_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
index ab8e85cd022b..ec86d0f863af 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class ThirdPartyVerificationTest {
@@ -34,15 +33,12 @@ public class ThirdPartyVerificationTest {
private static final String VALID_FILE_NAME = "valid.xml";
private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
- private Document mDoc = null;
-
/** Logic for setting up tests (empty if not yet needed). */
public static void main(String[] params) throws Exception {}
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for valid. */
@@ -50,6 +46,7 @@ public class ThirdPartyVerificationTest {
public void testValid() throws Exception {
System.out.println("starting testValid.");
testHrToOdThirdPartyVerification(VALID_FILE_NAME);
+ testOdToHrThirdPartyVerification(VALID_FILE_NAME);
}
/** Tests missing url. */
@@ -57,6 +54,7 @@ public class ThirdPartyVerificationTest {
public void testMissingUrl() throws Exception {
System.out.println("starting testMissingUrl.");
hrToOdExpectException(MISSING_URL_FILE_NAME);
+ odToHrExpectException(MISSING_URL_FILE_NAME);
}
private void hrToOdExpectException(String fileName) {
@@ -64,12 +62,26 @@ public class ThirdPartyVerificationTest {
new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_HR_PATH, fileName);
}
+ private void odToHrExpectException(String fileName) {
+ TestUtils.odToHrExpectException(
+ new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_OD_PATH, fileName);
+ }
+
private void testHrToOdThirdPartyVerification(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new ThirdPartyVerificationFactory(),
THIRD_PARTY_VERIFICATION_HR_PATH,
THIRD_PARTY_VERIFICATION_OD_PATH,
fileName);
}
+
+ private void testOdToHrThirdPartyVerification(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new ThirdPartyVerificationFactory(),
+ THIRD_PARTY_VERIFICATION_OD_PATH,
+ THIRD_PARTY_VERIFICATION_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
index 56503f7d6c6b..f49424061427 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -22,7 +22,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
@RunWith(JUnit4.class)
public class TransparencyInfoTest {
@@ -35,12 +34,9 @@ public class TransparencyInfoTest {
private static final String WITH_DEVELOPER_INFO_FILE_NAME = "with-developer-info.xml";
private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml";
- private Document mDoc = null;
-
@Before
public void setUp() throws Exception {
System.out.println("set up.");
- mDoc = TestUtils.document();
}
/** Test for transparency info valid empty. */
@@ -48,6 +44,7 @@ public class TransparencyInfoTest {
public void testTransparencyInfoValidEmptyFile() throws Exception {
System.out.println("starting testTransparencyInfoValidEmptyFile.");
testHrToOdTransparencyInfo(VALID_EMPTY_FILE_NAME);
+ testOdToHrTransparencyInfo(VALID_EMPTY_FILE_NAME);
}
/** Test for transparency info with developer info. */
@@ -55,6 +52,7 @@ public class TransparencyInfoTest {
public void testTransparencyInfoWithDeveloperInfo() throws Exception {
System.out.println("starting testTransparencyInfoWithDeveloperInfo.");
testHrToOdTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
+ testOdToHrTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
}
/** Test for transparency info with app info. */
@@ -62,14 +60,24 @@ public class TransparencyInfoTest {
public void testTransparencyInfoWithAppInfo() throws Exception {
System.out.println("starting testTransparencyInfoWithAppInfo.");
testHrToOdTransparencyInfo(WITH_APP_INFO_FILE_NAME);
+ testOdToHrTransparencyInfo(WITH_APP_INFO_FILE_NAME);
}
private void testHrToOdTransparencyInfo(String fileName) throws Exception {
TestUtils.testHrToOd(
- mDoc,
+ TestUtils.document(),
new TransparencyInfoFactory(),
TRANSPARENCY_INFO_HR_PATH,
TRANSPARENCY_INFO_OD_PATH,
fileName);
}
+
+ private void testOdToHrTransparencyInfo(String fileName) throws Exception {
+ TestUtils.testOdToHr(
+ TestUtils.document(),
+ new TransparencyInfoFactory(),
+ TRANSPARENCY_INFO_OD_PATH,
+ TRANSPARENCY_INFO_HR_PATH,
+ fileName);
+ }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
index 6a29b869be43..ea90993e0785 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Optional;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -98,6 +99,19 @@ public class TestUtils {
return outStream.toString(StandardCharsets.UTF_8);
}
+ /** Removes on-device style child with the corresponding name */
+ public static void removeOdChildEleWithName(Element ele, String childNameName) {
+ Optional<Element> childEle =
+ XmlUtils.asElementList(ele.getChildNodes()).stream()
+ .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(childNameName))
+ .findFirst();
+ if (childEle.isEmpty()) {
+ throw new IllegalStateException(
+ String.format("%s was not found in %s", childNameName, ele.getTagName()));
+ }
+ ele.removeChild(childEle.get());
+ }
+
/**
* Gets formatted XML for slightly more robust comparison checking than naive string comparison.
*/
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
new file mode 100644
index 000000000000..1aa3aa94ca6d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/missing-version.xml
@@ -0,0 +1,2 @@
+<bundle>
+</bundle> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml
new file mode 100644
index 000000000000..3fbe3599cd82
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml
@@ -0,0 +1,2 @@
+<pbundle_as_map name="safety_labels">
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml
new file mode 100644
index 000000000000..33b796552463
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml
@@ -0,0 +1,2 @@
+<pbundle_as_map name="system_app_safety_label">
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/missing-url.xml
new file mode 100644
index 000000000000..0b5a46f904e4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/thirdpartyverification/od/missing-url.xml
@@ -0,0 +1,2 @@
+<pbundle_as_map name="third_party_verification">
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
index 862bda465b25..d16caaea320f 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
@@ -7,5 +7,5 @@
countryRegion="US"
relationship="aosp"
website="example.com"
- appDeveloperRegistryId="registry_id" />
+ registryId="registry_id" />
</transparency-info> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
index 101c98bd8e60..d7a4e1a959b7 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
@@ -7,5 +7,6 @@
<string name="country_region" value="US"/>
<long name="relationship" value="5"/>
<string name="website" value="example.com"/>
+ <string name="app_developer_registry_id" value="registry_id"/>
</pbundle_as_map>
</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
index 36beb93319cd..8f854ad1107e 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -1,6 +1,4 @@
<app-metadata-bundles version="123">
- <system-app-safety-label url="www.example.com">
- </system-app-safety-label>
<safety-labels version="12345">
<data-labels>
<data-shared dataCategory="location"
@@ -21,6 +19,8 @@
<third-party-verification url="www.example.com">
</third-party-verification>
</safety-labels>
+ <system-app-safety-label url="www.example.com">
+ </system-app-safety-label>
<transparency-info>
<developer-info
name="max"
@@ -29,7 +29,7 @@
countryRegion="US"
relationship="aosp"
website="example.com"
- appDeveloperRegistryId="registry_id" />
+ registryId="registry_id" />
<app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
</transparency-info>
</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
index db21280ad61b..8f1dc6475b78 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -42,6 +42,7 @@
<string name="country_region" value="US"/>
<long name="relationship" value="5"/>
<string name="website" value="example.com"/>
+ <string name="app_developer_registry_id" value="registry_id"/>
</pbundle_as_map>
<pbundle_as_map name="app_info">
<string name="title" value="beervision"/>